Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
ScalePanel | 57 | 170 | 57 |
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.gui; | |
22 | ||
23 | import java.awt.Color; | |
24 | import java.awt.FontMetrics; | |
25 | import java.awt.Graphics; | |
26 | import java.awt.Graphics2D; | |
27 | import java.awt.Point; | |
28 | import java.awt.RenderingHints; | |
29 | import java.awt.event.ActionEvent; | |
30 | import java.awt.event.ActionListener; | |
31 | import java.awt.event.MouseEvent; | |
32 | import java.awt.event.MouseListener; | |
33 | import java.awt.event.MouseMotionListener; | |
34 | import java.beans.PropertyChangeEvent; | |
35 | import java.util.Iterator; | |
36 | import java.util.List; | |
37 | ||
38 | import javax.swing.JMenuItem; | |
39 | import javax.swing.JPanel; | |
40 | import javax.swing.JPopupMenu; | |
41 | import javax.swing.ToolTipManager; | |
42 | ||
43 | import jalview.datamodel.ColumnSelection; | |
44 | import jalview.datamodel.HiddenColumns; | |
45 | import jalview.datamodel.SequenceGroup; | |
46 | import jalview.renderer.ScaleRenderer; | |
47 | import jalview.renderer.ScaleRenderer.ScaleMark; | |
48 | import jalview.util.MessageManager; | |
49 | import jalview.util.Platform; | |
50 | import jalview.viewmodel.ViewportListenerI; | |
51 | import jalview.viewmodel.ViewportRanges; | |
52 | ||
53 | /** | |
54 | * The panel containing the sequence ruler (when not in wrapped mode), and | |
55 | * supports a range of mouse operations to select, hide or reveal columns. | |
56 | */ | |
57 | public class ScalePanel extends JPanel | |
58 | implements MouseMotionListener, MouseListener, ViewportListenerI | |
59 | { | |
60 | protected int offy = 4; | |
61 | ||
62 | public int width; | |
63 | ||
64 | protected AlignViewport av; | |
65 | ||
66 | AlignmentPanel ap; | |
67 | ||
68 | boolean stretchingGroup = false; | |
69 | ||
70 | /* | |
71 | * min, max hold the extent of a mouse drag action | |
72 | */ | |
73 | int min; | |
74 | ||
75 | int max; | |
76 | ||
77 | boolean mouseDragging = false; | |
78 | ||
79 | /* | |
80 | * holds a hidden column range when the mouse is over an adjacent column | |
81 | */ | |
82 | int[] reveal; | |
83 | ||
84 | /** | |
85 | * Constructor | |
86 | * | |
87 | * @param av | |
88 | * @param ap | |
89 | */ | |
90 | 478 | public ScalePanel(AlignViewport av, AlignmentPanel ap) |
91 | { | |
92 | 478 | this.av = av; |
93 | 478 | this.ap = ap; |
94 | ||
95 | 478 | addMouseListener(this); |
96 | 478 | addMouseMotionListener(this); |
97 | ||
98 | 478 | av.getRanges().addPropertyChangeListener(this); |
99 | } | |
100 | ||
101 | /** | |
102 | * DOCUMENT ME! | |
103 | * | |
104 | * @param evt | |
105 | * DOCUMENT ME! | |
106 | */ | |
107 | 3 | @Override |
108 | public void mousePressed(MouseEvent evt) | |
109 | { | |
110 | 3 | int res = ap.getSeqPanel().findAlignmentColumn(evt); |
111 | ||
112 | 3 | min = res; |
113 | 3 | max = res; |
114 | ||
115 | 3 | if (evt.isPopupTrigger()) // Mac: mousePressed |
116 | { | |
117 | 0 | rightMouseButtonPressed(evt, res); |
118 | 0 | return; |
119 | } | |
120 | 3 | if (Platform.isWinRightButton(evt)) |
121 | { | |
122 | /* | |
123 | * defer right-mouse click handling to mouse up on Windows | |
124 | * (where isPopupTrigger() will answer true) | |
125 | * but accept Cmd-click on Mac which passes isRightMouseButton | |
126 | */ | |
127 | 0 | return; |
128 | } | |
129 | 3 | leftMouseButtonPressed(evt, res); |
130 | } | |
131 | ||
132 | /** | |
133 | * Handles right mouse button press. If pressed in a selected column, opens | |
134 | * context menu for 'Hide Columns'. If pressed on a hidden columns marker, | |
135 | * opens context menu for 'Reveal / Reveal All'. Else does nothing. | |
136 | * | |
137 | * @param evt | |
138 | * @param res | |
139 | */ | |
140 | 0 | protected void rightMouseButtonPressed(MouseEvent evt, final int res) |
141 | { | |
142 | 0 | JPopupMenu pop = buildPopupMenu(res); |
143 | 0 | if (pop.getSubElements().length > 0) |
144 | { | |
145 | 0 | pop.show(this, evt.getX(), evt.getY()); |
146 | } | |
147 | } | |
148 | ||
149 | /** | |
150 | * Builds a popup menu with 'Hide' or 'Reveal' options, or both, or neither | |
151 | * | |
152 | * @param res | |
153 | * column number (0..) | |
154 | * @return | |
155 | */ | |
156 | 9 | protected JPopupMenu buildPopupMenu(final int res) |
157 | { | |
158 | 9 | JPopupMenu pop = new JPopupMenu(); |
159 | ||
160 | /* | |
161 | * logic here depends on 'reveal', set in mouseMoved; | |
162 | * grab the hidden range in case mouseMoved nulls it later | |
163 | */ | |
164 | 9 | final int[] hiddenRange = reveal; |
165 | 9 | if (hiddenRange != null) |
166 | { | |
167 | 6 | JMenuItem item = new JMenuItem( |
168 | MessageManager.getString("label.reveal")); | |
169 | 6 | item.addActionListener(new ActionListener() |
170 | { | |
171 | 0 | @Override |
172 | public void actionPerformed(ActionEvent e) | |
173 | { | |
174 | 0 | av.showColumn(hiddenRange[0]); |
175 | 0 | reveal = null; |
176 | 0 | ap.updateLayout(); |
177 | 0 | ap.paintAlignment(true, true); |
178 | 0 | av.sendSelection(); |
179 | } | |
180 | }); | |
181 | 6 | pop.add(item); |
182 | ||
183 | 6 | if (av.getAlignment().getHiddenColumns() |
184 | .hasMultiHiddenColumnRegions()) | |
185 | { | |
186 | 2 | item = new JMenuItem(MessageManager.getString("action.reveal_all")); |
187 | 2 | item.addActionListener(new ActionListener() |
188 | { | |
189 | 0 | @Override |
190 | public void actionPerformed(ActionEvent e) | |
191 | { | |
192 | 0 | av.showAllHiddenColumns(); |
193 | 0 | reveal = null; |
194 | 0 | ap.updateLayout(); |
195 | 0 | ap.paintAlignment(true, true); |
196 | 0 | av.sendSelection(); |
197 | } | |
198 | }); | |
199 | 2 | pop.add(item); |
200 | } | |
201 | } | |
202 | ||
203 | 9 | if (av.getColumnSelection().contains(res)) |
204 | { | |
205 | 4 | JMenuItem item = new JMenuItem( |
206 | MessageManager.getString("label.hide_columns")); | |
207 | 4 | item.addActionListener(new ActionListener() |
208 | { | |
209 | 0 | @Override |
210 | public void actionPerformed(ActionEvent e) | |
211 | { | |
212 | 0 | av.hideColumns(res, res); |
213 | 0 | if (av.getSelectionGroup() != null && av.getSelectionGroup() |
214 | .getSize() == av.getAlignment().getHeight()) | |
215 | { | |
216 | 0 | av.setSelectionGroup(null); |
217 | } | |
218 | ||
219 | 0 | ap.updateLayout(); |
220 | 0 | ap.paintAlignment(true, true); |
221 | 0 | av.sendSelection(); |
222 | } | |
223 | }); | |
224 | 4 | pop.add(item); |
225 | } | |
226 | 9 | return pop; |
227 | } | |
228 | ||
229 | /** | |
230 | * Handles left mouse button press | |
231 | * | |
232 | * @param evt | |
233 | * @param res | |
234 | */ | |
235 | 3 | protected void leftMouseButtonPressed(MouseEvent evt, final int res) |
236 | { | |
237 | /* | |
238 | * Ctrl-click/Cmd-click adds to the selection | |
239 | * Shift-click extends the selection | |
240 | */ | |
241 | // TODO Problem: right-click on Windows not reported until mouseReleased?!? | |
242 | 3 | if (!Platform.isControlDown(evt) && !evt.isShiftDown()) |
243 | { | |
244 | 3 | av.getColumnSelection().clear(); |
245 | } | |
246 | ||
247 | 3 | av.getColumnSelection().addElement(res); |
248 | 3 | SequenceGroup sg = new SequenceGroup(av.getAlignment().getSequences()); |
249 | 3 | sg.setStartRes(res); |
250 | 3 | sg.setEndRes(res); |
251 | ||
252 | 3 | if (evt.isShiftDown()) |
253 | { | |
254 | 0 | int min = Math.min(av.getColumnSelection().getMin(), res); |
255 | 0 | int max = Math.max(av.getColumnSelection().getMax(), res); |
256 | 0 | for (int i = min; i < max; i++) |
257 | { | |
258 | 0 | av.getColumnSelection().addElement(i); |
259 | } | |
260 | 0 | sg.setStartRes(min); |
261 | 0 | sg.setEndRes(max); |
262 | } | |
263 | 3 | av.setSelectionGroup(sg); |
264 | 3 | ap.paintAlignment(false, false); |
265 | 3 | PaintRefresher.Refresh(this, av.getSequenceSetId()); |
266 | 3 | av.sendSelection(); |
267 | } | |
268 | ||
269 | /** | |
270 | * Action on mouseUp is to set the limit of the current selection group (if | |
271 | * there is one) and broadcast the selection | |
272 | * | |
273 | * @param evt | |
274 | */ | |
275 | 3 | @Override |
276 | public void mouseReleased(MouseEvent evt) | |
277 | { | |
278 | 3 | boolean wasDragging = mouseDragging; |
279 | 3 | mouseDragging = false; |
280 | 3 | ap.getSeqPanel().stopScrolling(); |
281 | ||
282 | 3 | int res = ap.getSeqPanel().findAlignmentColumn(evt); |
283 | ||
284 | 3 | if (!stretchingGroup) |
285 | { | |
286 | 0 | if (evt.isPopupTrigger()) // Windows: mouseReleased |
287 | { | |
288 | 0 | rightMouseButtonPressed(evt, res); |
289 | } | |
290 | else | |
291 | { | |
292 | 0 | ap.paintAlignment(false, false); |
293 | } | |
294 | 0 | return; |
295 | } | |
296 | ||
297 | 3 | SequenceGroup sg = av.getSelectionGroup(); |
298 | ||
299 | 3 | if (sg != null) |
300 | { | |
301 | 3 | if (res > sg.getStartRes()) |
302 | { | |
303 | 2 | sg.setEndRes(res); |
304 | } | |
305 | 1 | else if (res < sg.getStartRes()) |
306 | { | |
307 | 0 | sg.setStartRes(res); |
308 | } | |
309 | 3 | if (wasDragging) |
310 | { | |
311 | 3 | min = Math.min(res, min); |
312 | 3 | max = Math.max(res, max); |
313 | 3 | av.getColumnSelection().stretchGroup(res, sg, min, max); |
314 | } | |
315 | } | |
316 | 3 | stretchingGroup = false; |
317 | 3 | ap.paintAlignment(false, false); |
318 | 3 | av.isSelectionGroupChanged(true); |
319 | 3 | av.isColSelChanged(true); |
320 | 3 | PaintRefresher.Refresh(ap, av.getSequenceSetId()); |
321 | 3 | av.sendSelection(); |
322 | } | |
323 | ||
324 | /** | |
325 | * Action on dragging the mouse in the scale panel is to expand or shrink the | |
326 | * selection group range (including any hidden columns that it spans). Note | |
327 | * that the selection is only broadcast at the start of the drag (on | |
328 | * mousePressed) and at the end (on mouseReleased), to avoid overload | |
329 | * redrawing of other views. | |
330 | * | |
331 | * @param evt | |
332 | */ | |
333 | 3 | @Override |
334 | public void mouseDragged(MouseEvent evt) | |
335 | { | |
336 | 3 | mouseDragging = true; |
337 | 3 | int res = ap.getSeqPanel().findAlignmentColumn(evt); |
338 | ||
339 | 3 | ColumnSelection cs = av.getColumnSelection(); |
340 | ||
341 | 3 | min = Math.min(res, min); |
342 | 3 | max = Math.max(res, max); |
343 | ||
344 | 3 | SequenceGroup sg = av.getSelectionGroup(); |
345 | 3 | if (sg != null) |
346 | { | |
347 | 3 | stretchingGroup = true; |
348 | 3 | cs.stretchGroup(res, sg, min, max); |
349 | 3 | ap.paintAlignment(false, false); |
350 | 3 | PaintRefresher.Refresh(ap, av.getSequenceSetId()); |
351 | } | |
352 | } | |
353 | ||
354 | 0 | @Override |
355 | public void mouseEntered(MouseEvent evt) | |
356 | { | |
357 | 0 | if (mouseDragging) |
358 | { | |
359 | 0 | mouseDragging = false; |
360 | 0 | ap.getSeqPanel().stopScrolling(); |
361 | } | |
362 | } | |
363 | ||
364 | /** | |
365 | * Action on leaving the panel bounds with mouse drag in progress is to start | |
366 | * scrolling the alignment in the direction of the mouse. To restrict | |
367 | * scrolling to left-right (not up-down), the y-value of the mouse position is | |
368 | * replaced with zero. | |
369 | */ | |
370 | 0 | @Override |
371 | public void mouseExited(MouseEvent evt) | |
372 | { | |
373 | 0 | if (mouseDragging) |
374 | { | |
375 | 0 | ap.getSeqPanel().startScrolling(new Point(evt.getX(), 0)); |
376 | } | |
377 | } | |
378 | ||
379 | 0 | @Override |
380 | public void mouseClicked(MouseEvent evt) | |
381 | { | |
382 | } | |
383 | ||
384 | /** | |
385 | * Creates a tooltip when the mouse is over a hidden columns marker | |
386 | */ | |
387 | 8 | @Override |
388 | public void mouseMoved(MouseEvent evt) | |
389 | { | |
390 | 8 | this.setToolTipText(null); |
391 | 8 | reveal = null; |
392 | 8 | final int res = ap.getSeqPanel().findAlignmentColumn(evt); |
393 | ||
394 | 8 | highlightAllStructPos(res); |
395 | 8 | if (!av.hasHiddenColumns()) |
396 | { | |
397 | 0 | return; |
398 | } | |
399 | 8 | reveal = av.getAlignment().getHiddenColumns() |
400 | .getRegionWithEdgeAtRes(av.getAlignment().getHiddenColumns() | |
401 | .absoluteToVisibleColumn(res)); | |
402 | 8 | if (reveal == null) |
403 | { | |
404 | 3 | return; |
405 | } | |
406 | 5 | ToolTipManager.sharedInstance().registerComponent(this); |
407 | 5 | this.setToolTipText( |
408 | MessageManager.getString("label.reveal_hidden_columns")); | |
409 | 5 | repaint(); |
410 | } | |
411 | ||
412 | 8 | public void highlightAllStructPos(int col) |
413 | { | |
414 | 8 | ap.getStructureSelectionManager().highlightPositionsOnMany( |
415 | ap.av.getAlignment().getSequencesArray(), new int[] | |
416 | { col, col }, ap); | |
417 | ||
418 | } | |
419 | ||
420 | /** | |
421 | * DOCUMENT ME! | |
422 | * | |
423 | * @param g | |
424 | * DOCUMENT ME! | |
425 | */ | |
426 | 2636 | @Override |
427 | public void paintComponent(Graphics g) | |
428 | { | |
429 | // super.paintComponent(g); // BH 2019 | |
430 | ||
431 | /* | |
432 | * shouldn't get called in wrapped mode as the scale above is | |
433 | * drawn instead by SeqCanvas.drawNorthScale | |
434 | */ | |
435 | 2636 | if (!av.getWrapAlignment()) |
436 | { | |
437 | 2379 | drawScale(g, av.getRanges().getStartRes(), av.getRanges().getEndRes(), |
438 | getWidth(), getHeight()); | |
439 | } | |
440 | } | |
441 | ||
442 | // scalewidth will normally be screenwidth, | |
443 | 2417 | public void drawScale(Graphics g, int startx, int endx, int width, |
444 | int height) | |
445 | { | |
446 | 2417 | Graphics2D gg = (Graphics2D) g; |
447 | 2417 | gg.setFont(av.getFont()); |
448 | ||
449 | 2417 | if (av.antiAlias) |
450 | { | |
451 | 761 | gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, |
452 | RenderingHints.VALUE_ANTIALIAS_ON); | |
453 | } | |
454 | ||
455 | // Fill in the background | |
456 | 2417 | gg.setColor(Color.white); |
457 | 2417 | gg.fillRect(0, 0, width, height); |
458 | 2417 | gg.setColor(Color.black); |
459 | ||
460 | // Fill the selected columns | |
461 | 2417 | ColumnSelection cs = av.getColumnSelection(); |
462 | 2417 | HiddenColumns hidden = av.getAlignment().getHiddenColumns(); |
463 | 2417 | int avCharWidth = av.getCharWidth(); |
464 | 2417 | int avCharHeight = av.getCharHeight(); |
465 | ||
466 | 2417 | if (cs != null) |
467 | { | |
468 | 2417 | gg.setColor(new Color(220, 0, 0)); |
469 | ||
470 | 2417 | for (int sel : cs.getSelected()) |
471 | { | |
472 | // TODO: JAL-2001 - provide a fast method to list visible selected in a | |
473 | // given range | |
474 | ||
475 | 28 | if (av.hasHiddenColumns()) |
476 | { | |
477 | 0 | if (hidden.isVisible(sel)) |
478 | { | |
479 | 0 | sel = hidden.absoluteToVisibleColumn(sel); |
480 | } | |
481 | else | |
482 | { | |
483 | 0 | continue; |
484 | } | |
485 | } | |
486 | ||
487 | 28 | if ((sel >= startx) && (sel <= endx)) |
488 | { | |
489 | 28 | gg.fillRect((sel - startx) * avCharWidth, 0, avCharWidth, |
490 | getHeight()); | |
491 | } | |
492 | } | |
493 | } | |
494 | ||
495 | 2416 | int widthx = 1 + endx - startx; |
496 | ||
497 | 2416 | FontMetrics fm = gg.getFontMetrics(av.getFont()); |
498 | 2416 | int y = avCharHeight; |
499 | 2416 | int yOf = fm.getDescent(); |
500 | 2416 | y -= yOf; |
501 | 2416 | if (av.hasHiddenColumns()) |
502 | { | |
503 | // draw any hidden column markers | |
504 | 292 | gg.setColor(Color.blue); |
505 | 292 | int res; |
506 | ||
507 | 292 | if (av.getShowHiddenMarkers()) |
508 | { | |
509 | 292 | Iterator<Integer> it = hidden.getStartRegionIterator(startx, |
510 | startx + widthx + 1); | |
511 | 575 | while (it.hasNext()) |
512 | { | |
513 | 283 | res = it.next() - startx; |
514 | ||
515 | 283 | gg.fillPolygon( |
516 | new int[] | |
517 | { -1 + res * avCharWidth - avCharHeight / 4, | |
518 | -1 + res * avCharWidth + avCharHeight / 4, | |
519 | -1 + res * avCharWidth }, | |
520 | new int[] | |
521 | { y, y, y + 2 * yOf }, 3); | |
522 | } | |
523 | } | |
524 | } | |
525 | // Draw the scale numbers | |
526 | 2416 | gg.setColor(Color.black); |
527 | ||
528 | 2416 | int maxX = 0; |
529 | 2416 | List<ScaleMark> marks = new ScaleRenderer().calculateMarks(av, startx, |
530 | endx); | |
531 | ||
532 | 2416 | for (ScaleMark mark : marks) |
533 | { | |
534 | 23137 | boolean major = mark.major; |
535 | 23137 | int mpos = mark.column; // (i - startx - 1) |
536 | 23137 | String mstring = mark.text; |
537 | 23137 | if (mstring != null) |
538 | { | |
539 | 11088 | if (mpos * avCharWidth > maxX) |
540 | { | |
541 | 10815 | gg.drawString(mstring, mpos * avCharWidth, y); |
542 | 10815 | maxX = (mpos + 2) * avCharWidth + fm.stringWidth(mstring); |
543 | } | |
544 | } | |
545 | 23137 | if (major) |
546 | { | |
547 | 11088 | gg.drawLine((mpos * avCharWidth) + (avCharWidth / 2), y + 2, |
548 | (mpos * avCharWidth) + (avCharWidth / 2), y + (yOf * 2)); | |
549 | } | |
550 | else | |
551 | { | |
552 | 12049 | gg.drawLine((mpos * avCharWidth) + (avCharWidth / 2), y + yOf, |
553 | (mpos * avCharWidth) + (avCharWidth / 2), y + (yOf * 2)); | |
554 | } | |
555 | } | |
556 | } | |
557 | ||
558 | 644 | @Override |
559 | public void propertyChange(PropertyChangeEvent evt) | |
560 | { | |
561 | // Respond to viewport change events (e.g. alignment panel was scrolled) | |
562 | // Both scrolling and resizing change viewport ranges: scrolling changes | |
563 | // both start and end points, but resize only changes end values. | |
564 | // Here we only want to fastpaint on a scroll, with resize using a normal | |
565 | // paint, so scroll events are identified as changes to the horizontal or | |
566 | // vertical start value. | |
567 | 644 | if (evt.getPropertyName().equals(ViewportRanges.STARTRES) |
568 | || evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ) | |
569 | || evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT)) | |
570 | { | |
571 | // scroll event, repaint panel | |
572 | ||
573 | // Call repaint on alignment panel so that repaints from other alignment | |
574 | // panel components can be aggregated. Otherwise performance of the | |
575 | // overview | |
576 | // window and others may be adversely affected. | |
577 | 36 | av.getAlignPanel().repaint(); |
578 | } | |
579 | } | |
580 | ||
581 | } |