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