X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FAnnotationPanel.java;h=ce731adbb1501605378b6a3223e8e23f8ac2feba;hb=71a3cc0202bd8b731f01e5706372e9bae047d592;hp=0f025b594eb0150c55448b36b80cbce7d5b6d509;hpb=b0043a9653c4e41d358052c6d59dacf623c244f5;p=jalview.git diff --git a/src/jalview/gui/AnnotationPanel.java b/src/jalview/gui/AnnotationPanel.java index 0f025b5..e9ef19c 100755 --- a/src/jalview/gui/AnnotationPanel.java +++ b/src/jalview/gui/AnnotationPanel.java @@ -1,1181 +1,1373 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer - * Copyright (C) 2005 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - */ -package jalview.gui; - -import jalview.datamodel.*; - -import java.awt.*; -import java.awt.event.*; -import java.awt.image.*; - -import javax.swing.*; - - -/** - * DOCUMENT ME! - * - * @author $author$ - * @version $Revision$ - */ -public class AnnotationPanel extends JPanel implements MouseListener, - MouseMotionListener, ActionListener, AdjustmentListener -{ - final String HELIX = "Helix"; - final String SHEET = "Sheet"; - final String LABEL = "Label"; - final String REMOVE = "Remove Annotation"; - final String COLOUR = "Colour"; - final Color HELIX_COLOUR = Color.red.darker(); - final Color SHEET_COLOUR = Color.green.darker().darker(); - - /** DOCUMENT ME!! */ - AlignViewport av; - AlignmentPanel ap; - int activeRow = -1; - BufferedImage image; - Graphics2D gg; - FontMetrics fm; - int imgWidth = 0; - boolean fastPaint = false; - - //Used For mouse Dragging and resizing graphs - int graphStretch = -1; - int graphStretchY = -1; - int min; //used by mouseDragged to see if user - int max; //used by mouseDragged to see if user - boolean mouseDragging = false; - - boolean MAC = false; - - /** - * Creates a new AnnotationPanel object. - * - * @param ap DOCUMENT ME! - */ - public AnnotationPanel(AlignmentPanel ap) - { - - if(System.getProperty("os.name").startsWith("Mac")) - MAC = true; - - ToolTipManager.sharedInstance().registerComponent(this); - ToolTipManager.sharedInstance().setInitialDelay(0); - ToolTipManager.sharedInstance().setDismissDelay(10000); - this.ap = ap; - av = ap.av; - this.setLayout(null); - addMouseListener(this); - addMouseMotionListener(this); - ap.annotationScroller.getVerticalScrollBar().addAdjustmentListener(this); - } - - public AnnotationPanel(AlignViewport av) - { - this.av = av; - } - - - /** - * DOCUMENT ME! - * - * @param evt DOCUMENT ME! - */ - public void adjustmentValueChanged(AdjustmentEvent evt) - { - ap.alabels.setScrollOffset(-evt.getValue()); - } - - /** - * DOCUMENT ME! - */ - public int adjustPanelHeight() - { - // setHeight of panels - image = null; - AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation(); - int height = 0; - - if (aa != null) - { - for (int i = 0; i < aa.length; i++) - { - if (!aa[i].visible) - { - continue; - } - - aa[i].height = 0; - - if (aa[i].hasText) - { - aa[i].height += av.charHeight; - } - - if (aa[i].hasIcons) - { - aa[i].height += 16; - } - - if (aa[i].graph>0) - { - aa[i].height += aa[i].graphHeight; - } - - if (aa[i].height == 0) - { - aa[i].height = 20; - } - - height += aa[i].height; - } - } - else - { - height = 20; - } - - this.setPreferredSize(new Dimension(1, height)); - - return height; - } - - - - /** - * DOCUMENT ME! - * - * @param evt DOCUMENT ME! - */ - public void actionPerformed(ActionEvent evt) - { - AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation(); - Annotation[] anot = aa[activeRow].annotations; - - if (evt.getActionCommand().equals(REMOVE)) - { - for (int i = 0; i < av.getColumnSelection().size(); i++) - { - anot[av.getColumnSelection().columnAt(i)] = null; - } - } - else if (evt.getActionCommand().equals(LABEL)) - { - String label = JOptionPane.showInputDialog(this, "Enter Label ", - "Enter label", JOptionPane.QUESTION_MESSAGE); - - if (label == null) - { - return; - } - - if ((label.length() > 0) && !aa[activeRow].hasText) - { - aa[activeRow].hasText = true; - } - - for (int i = 0; i < av.getColumnSelection().size(); i++) - { - int index = av.getColumnSelection().columnAt(i); - - if (anot[index] == null) - { - anot[index] = new Annotation(label, "", ' ', 0); - } - - anot[index].displayCharacter = label; - } - } - else if (evt.getActionCommand().equals(COLOUR)) - { - Color col = JColorChooser.showDialog(this, - "Choose foreground colour", Color.black); - - for (int i = 0; i < av.getColumnSelection().size(); i++) - { - int index = av.getColumnSelection().columnAt(i); - - if (anot[index] == null) - { - anot[index] = new Annotation("", "", ' ', 0); - } - - anot[index].colour = col; - } - } - else // HELIX OR SHEET - { - char type = 0; - String symbol = "\u03B1"; - - if (evt.getActionCommand().equals(HELIX)) - { - type = 'H'; - } - else if (evt.getActionCommand().equals(SHEET)) - { - type = 'E'; - symbol = "\u03B2"; - } - - if (!aa[activeRow].hasIcons) - { - aa[activeRow].hasIcons = true; - } - - String label = JOptionPane.showInputDialog("Enter a label for the structure?", - symbol); - - if (label == null) - { - return; - } - - if ((label.length() > 0) && !aa[activeRow].hasText) - { - aa[activeRow].hasText = true; - } - - for (int i = 0; i < av.getColumnSelection().size(); i++) - { - int index = av.getColumnSelection().columnAt(i); - - if (anot[index] == null) - { - anot[index] = new Annotation(label, "", type, 0); - } - - anot[index].secondaryStructure = type; - anot[index].displayCharacter = label; - } - } - - adjustPanelHeight(); - repaint(); - - return; - } - - /** - * DOCUMENT ME! - * - * @param evt DOCUMENT ME! - */ - public void mousePressed(MouseEvent evt) - { - - - AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation(); - if(aa==null) - return; - - - int height = 0; - activeRow = -1; - - - for (int i = 0; i < aa.length; i++) - { - if (aa[i].visible) - { - height += aa[i].height; - } - - if (evt.getY() < height) - { - if (aa[i].editable) - { - activeRow = i; - } - else if(aa[i].graph>0) - { - //Stretch Graph - graphStretch = i; - graphStretchY = evt.getY(); - } - - break; - } - } - - - if (SwingUtilities.isRightMouseButton(evt)) - { - if (av.getColumnSelection() == null) - { - return; - } - - JPopupMenu pop = new JPopupMenu("Structure type"); - JMenuItem item = new JMenuItem(HELIX); - item.addActionListener(this); - pop.add(item); - item = new JMenuItem(SHEET); - item.addActionListener(this); - pop.add(item); - item = new JMenuItem(LABEL); - item.addActionListener(this); - pop.add(item); - item = new JMenuItem(COLOUR); - item.addActionListener(this); - pop.add(item); - item = new JMenuItem(REMOVE); - item.addActionListener(this); - pop.add(item); - pop.show(this, evt.getX(), evt.getY()); - - return; - } - - if (aa == null) - { - return; - } - - int res = (evt.getX() / av.getCharWidth()) + av.getStartRes(); - - min = res; - max = res; - - if (av.getColumnSelection().contains(res)) - av.getColumnSelection().removeElement(res); - else - { - av.getColumnSelection().addElement(res); - SequenceGroup sg = new SequenceGroup(); - - for (int i = 0; i < av.alignment.getSequences().size(); i++) - { - sg.addSequence(av.alignment.getSequenceAt(i), false); - } - - sg.setStartRes(res); - sg.setEndRes(res); - av.setSelectionGroup(sg); - } - - ap.repaint(); - - } - - /** - * DOCUMENT ME! - * - * @param evt DOCUMENT ME! - */ - public void mouseReleased(MouseEvent evt) - { - graphStretch = -1; - graphStretchY = -1; - mouseDragging = false; - } - - /** - * DOCUMENT ME! - * - * @param evt DOCUMENT ME! - */ - public void mouseEntered(MouseEvent evt) - { - if(mouseDragging) - ap.seqPanel.scrollCanvas(null); - } - - /** - * DOCUMENT ME! - * - * @param evt DOCUMENT ME! - */ - public void mouseExited(MouseEvent evt) - { - if(mouseDragging) - ap.seqPanel.scrollCanvas(evt); - } - - - /** - * DOCUMENT ME! - * - * @param evt DOCUMENT ME! - */ - public void mouseDragged(MouseEvent evt) - { - if(graphStretch>-1) - { - av.alignment.getAlignmentAnnotation()[graphStretch].graphHeight += graphStretchY - evt.getY(); - if(av.alignment.getAlignmentAnnotation()[graphStretch].graphHeight <10) - av.alignment.getAlignmentAnnotation()[graphStretch].graphHeight = 10; - graphStretchY = evt.getY(); - adjustPanelHeight(); - ap.repaint(); - } - else - { - mouseDragging = true; - - int res = (evt.getX() / av.getCharWidth()) + av.getStartRes(); - - SequenceGroup sg = av.getSelectionGroup(); - - if (res < min) - { - min = res; - } - - if (res > max) - { - max = res; - } - - if (sg != null) - { - if (!av.getColumnSelection().contains(res)) - { - av.getColumnSelection().addElement(res); - } - - if (res > sg.getStartRes()) - { - sg.setEndRes(res); - } - else if (res < sg.getStartRes()) - { - sg.setStartRes(res); - } - - for (int i = min; i <= max; i++) - { - if ((i < sg.getStartRes()) || (i > sg.getEndRes())) - { - av.getColumnSelection().removeElement(i); - } - else - { - av.getColumnSelection().addElement(i); - } - } - - ap.repaint(); - } - - } - } - - /** - * DOCUMENT ME! - * - * @param evt DOCUMENT ME! - */ - public void mouseMoved(MouseEvent evt) - { - AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation(); - - if (aa == null) - { - return; - } - - int row = -1; - int height = 0; - - for (int i = 0; i < aa.length; i++) - { - if (aa[i].visible) - { - height += aa[i].height; - } - - if (evt.getY() < height) - { - row = i; - - break; - } - } - - int res = (evt.getX() / av.getCharWidth()) + av.getStartRes(); - - if(av.hasHiddenColumns) - res = av.getColumnSelection().adjustForHiddenColumns(res); - - if (row > -1 && res-1) - { - StringBuffer tip = new StringBuffer(""); - for (int gg = 0; gg < aa.length; gg++) - { - if (aa[gg].graphGroup == aa[row].graphGroup && aa[gg].annotations[res]!=null) - tip.append(aa[gg].label+" "+aa[gg].annotations[res].description+"
" ); - } - if(tip.length()!=6) - { - tip.setLength(tip.length() - 4); - this.setToolTipText(tip.toString() + ""); - } - } - else if(aa[row].annotations[res] != null) - this.setToolTipText(aa[row].annotations[res].description); - - if(aa[row].annotations[res]!=null) - { - StringBuffer text = new StringBuffer("Sequence position " + - (res + 1) + " " + - aa[row].annotations[res].description); - - ap.alignFrame.statusBar.setText(text.toString()); - } - } - } - - /** - * DOCUMENT ME! - * - * @param evt DOCUMENT ME! - */ - public void mouseClicked(MouseEvent evt) - { - } - - /** - * DOCUMENT ME! - * - * @param g DOCUMENT ME! - */ - public void paintComponent(Graphics g) - { - g.setColor(Color.white); - g.fillRect(0, 0, getWidth(), getHeight()); - - if(image!=null) - {if (fastPaint - || (getVisibleRect().width != g.getClipBounds().width) - || (getVisibleRect().height != g.getClipBounds().height)) - { - g.drawImage(image, 0, 0, this); - fastPaint = false; - return; - } - } - imgWidth = (av.endRes - av.startRes + 1) * av.charWidth; - - if (image == null || imgWidth != image.getWidth() - || image.getHeight(this) != getHeight()) - { - image = new BufferedImage(imgWidth, ap.annotationPanel.getHeight(), - BufferedImage.TYPE_INT_RGB); - gg = (Graphics2D) image.getGraphics(); - - if(av.antiAlias) - gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - - gg.setFont(av.getFont()); - fm = gg.getFontMetrics(); - } - - - drawComponent(gg, av.startRes, av.endRes + 1); - g.drawImage(image, 0, 0, this); - } - - /** - * DOCUMENT ME! - * - * @param horizontal DOCUMENT ME! - */ - public void fastPaint(int horizontal) - { - if ((horizontal == 0) || gg==null || - (av.alignment.getAlignmentAnnotation() == null) || - (av.alignment.getAlignmentAnnotation().length < 1)) - { - repaint(); - return; - } - - gg.copyArea(0, 0, imgWidth, getHeight(), -horizontal * av.charWidth, 0); - - int sr = av.startRes; - int er = av.endRes + 1; - int transX = 0; - - if (horizontal > 0) // scrollbar pulled right, image to the left - { - transX = (er - sr - horizontal) * av.charWidth; - sr = er - horizontal; - } - else if (horizontal < 0) - { - er = sr - horizontal; - } - - gg.translate(transX, 0); - - drawComponent(gg, sr, er); - - gg.translate(-transX, 0); - - fastPaint = true; - - repaint(); - - } - - /** - * DOCUMENT ME! - * - * @param g DOCUMENT ME! - * @param startRes DOCUMENT ME! - * @param endRes DOCUMENT ME! - */ - public void drawComponent(Graphics g, int startRes, int endRes) - { - g.setFont(av.getFont()); - - if (fm == null) - fm = g.getFontMetrics(); - - - g.setColor(Color.white); - g.fillRect(0, 0, (endRes - startRes) * av.charWidth, getHeight()); - - if ( (av.alignment.getAlignmentAnnotation() == null) || - (av.alignment.getAlignmentAnnotation().length < 1)) - { - g.setColor(Color.white); - g.fillRect(0, 0, getWidth(), getHeight()); - g.setColor(Color.black); - if(av.validCharWidth) - g.drawString("Alignment has no annotations", 20, 15); - - return; - } - - AlignmentAnnotation[] aa = av.alignment.getAlignmentAnnotation(); - - int x = 0, y = 0; - int column=0; - char lastSS; - int lastSSX; - int iconOffset = av.charHeight / 2; - boolean validRes = false; - - boolean [] graphGroupDrawn = new boolean[aa.length]; - - - //\u03B2 \u03B1 - for (int i = 0; i < aa.length; i++) - { - AlignmentAnnotation row = aa[i]; - - if (!row.visible) - { - continue; - } - - lastSS = ' '; - lastSSX = 0; - - if (row.graph>0) - { - if(row.graphGroup>-1 && graphGroupDrawn[ row.graphGroup ] ) - continue; - - // this is so that we draw the characters below the graph - y += row.height; - - if (row.hasText) - { - y -= av.charHeight; - } - } - - if (row.hasText) - { - iconOffset = av.charHeight / 2; - } - else - { - iconOffset = 0; - } - - x = 0; - while (x < endRes-startRes) - { - if (av.hasHiddenColumns) - { - column = av.getColumnSelection().adjustForHiddenColumns(startRes+x); - if (column > row.annotations.length-1) - { - break; - } - } - else - column = startRes+x; - - - if ((row.annotations.length <= column) || - (row.annotations[column] == null)) - { - validRes = false; - } - else - { - validRes = true; - } - - - if (activeRow == i) - { - g.setColor(Color.red); - - if (av.getColumnSelection() != null) - { - for (int n = 0; n < av.getColumnSelection().size(); n++) - { - int v = av.getColumnSelection().columnAt(n); - - if (v == column) - { - g.fillRect(x * av.charWidth, y, - av.charWidth, av.charHeight); - } - } - } - } - - if (av.validCharWidth && validRes && - (row.annotations[column].displayCharacter.length() > 0)) - { - - int charOffset = (av.charWidth - - fm.charWidth(row.annotations[column].displayCharacter.charAt( - 0))) / 2; - g.setColor(row.annotations[column].colour); - - if (column == 0 || row.graph>0) - { - g.drawString(row.annotations[column].displayCharacter, - (x*av.charWidth)+charOffset, - y + iconOffset + 3); - } - else if ( - row.annotations[column - 1] == null - ||(!row.annotations[column].displayCharacter.equals( - row.annotations[column - 1].displayCharacter) - || - (row.annotations[column].displayCharacter.length() <2 && - row.annotations[column].secondaryStructure==' '))) - { - g.drawString(row.annotations[column].displayCharacter, - x*av.charWidth+charOffset, - y + iconOffset + 3); - } - } - - if (row.hasIcons) - { - if (!validRes || - (row.annotations[column].secondaryStructure != lastSS)) - { - switch (lastSS) - { - case 'H': - g.setColor(HELIX_COLOUR); - if(MAC) - { - //Off by 1 offset when drawing rects and ovals - //to offscreen image on the MAC - g.fillRoundRect(lastSSX, y + 4 + iconOffset, - x*av.charWidth - lastSSX, 7, 8, 8); - break; - } - - int sCol = (lastSSX / av.charWidth) + startRes; - int x1 = lastSSX; - int x2 = x*av.charWidth; - - if(sCol==0 || - row.annotations[sCol-1]==null || - row.annotations[sCol-1].secondaryStructure!='H') - { - g.fillArc(lastSSX, y+4+iconOffset, av.charWidth+1, 7, 90,180) ; - x1 += av.charWidth/2; - } - - if(row.annotations[column]==null || - row.annotations[column].secondaryStructure!='H') - { - g.fillArc(x*av.charWidth-av.charWidth, - y+4+iconOffset, - av.charWidth, - 7, 270,180); - x2 -= av.charWidth/2; - } - - g.fillRect(x1, y+4+iconOffset, x2-x1+1, 7); - break; - - case 'E': - g.setColor(SHEET_COLOUR); - g.fillRect(lastSSX, y + 4 + iconOffset, - x*av.charWidth - lastSSX - 4, 7); - g.fillPolygon(new int[] { x*av.charWidth - 5, - x*av.charWidth - 5, - x*av.charWidth }, - new int[] - { - y + iconOffset, y + 14 + iconOffset, - y + 7 + iconOffset - }, 3); - - break; - - - default: - g.setColor(Color.gray); - g.fillRect(lastSSX, y + 6 + iconOffset, - x*av.charWidth - lastSSX, 2); - - break; - } - - if (validRes) - { - lastSS = row.annotations[column].secondaryStructure; - } - else - { - lastSS = ' '; - } - - lastSSX = x*av.charWidth; - } - } - column++; - x++; - } - - if(column>=row.annotations.length) - column = row.annotations.length-1; - - x ++; - - if (row.hasIcons) - { - switch (lastSS) - { - case 'H': - g.setColor(HELIX_COLOUR); - if (MAC) - { - //Off by 1 offset when drawing rects and ovals - //to offscreen image on the MAC - g.fillRoundRect(lastSSX, y + 4 + iconOffset, - x*av.charWidth - lastSSX, 7, 8, 8); - break; - } - - - int sCol = (lastSSX / av.charWidth) + startRes; - int x1 = lastSSX; - int x2 = x; - - if(sCol==0 || - row.annotations[sCol-1]==null || - row.annotations[sCol-1].secondaryStructure!='H') - { - g.fillArc(lastSSX, y+4+iconOffset, av.charWidth+1, 7, 90,180) ; - x1 += av.charWidth/2; - } - - if(row.annotations[column]==null || - row.annotations[column].secondaryStructure!='H') - { - g.fillArc(x-av.charWidth, y+4+iconOffset, av.charWidth, 7, 270,180); - x2 -= av.charWidth/2; - } - - g.fillRect(x1, y+4+iconOffset, x2-x1+1, 7); - - break; - - case 'E': - g.setColor(SHEET_COLOUR); - - if (row.annotations[endRes] ==null - || row.annotations[endRes].secondaryStructure != 'E') - { - g.fillRect(lastSSX, y + 4 + iconOffset, - x*av.charWidth - lastSSX - 4, 7); - g.fillPolygon(new int[] - {x*av.charWidth - 5, - x*av.charWidth - 5, - x*av.charWidth}, - new int[] - { - y + iconOffset, y + 14 + iconOffset, - y + 7 + iconOffset - }, 3); - } - else - g.fillRect(lastSSX, y + 4 + iconOffset, - x*av.charWidth - lastSSX, 7); - - break; - - - default: - g.setColor(Color.gray); - if(!av.wrapAlignment || endRes==av.endRes) - g.fillRect(lastSSX, y + 6 + iconOffset, - x*av.charWidth - lastSSX, 2); - - break; - } - } - - if (row.graph>0) - { - if(row.graph == AlignmentAnnotation.LINE_GRAPH ) - { - if(row.graphGroup>-1 && !graphGroupDrawn[row.graphGroup]) - { - float groupmax=-999999, groupmin=9999999; - for(int gg=0; gggroupmax) - groupmax = aa[gg].graphMax; - if(aa[gg].graphMin0 && row.hasText) - { - y += av.charHeight; - } - - if (row.graph==0) - { - y += aa[i].height; - } - } - } - - public void drawLineGraph(Graphics g, AlignmentAnnotation aa, - int sRes, int eRes, - int y, - float min, float max, - int graphHeight) - { - if(sRes>aa.annotations.length) - return; - - - int x = 0; - - //Adjustment for fastpaint to left - if(eRes aaMax) - { - break; - } - - if(aa.annotations[column]==null || aa.annotations[column-1]==null) - { - x++; - continue; - } - - - g.setColor(aa.annotations[column].colour); - y1 = y - (int) (((aa.annotations[column-1].value-min) / range) * graphHeight); - y2 = y - (int) (((aa.annotations[column].value-min) / range) * graphHeight); - - g.drawLine(x*av.charWidth-av.charWidth/2, y1, x*av.charWidth+av.charWidth/2, y2); - x ++; - } - - if(aa.threshold!=null) - { - g.setColor(aa.threshold.colour); - Graphics2D g2 = (Graphics2D)g; - g2.setStroke(new BasicStroke(1, - BasicStroke.CAP_SQUARE, - BasicStroke.JOIN_ROUND, 3f, - new float[] { 5f, 3f }, 0f)); - - y2 = (int)(y - ((aa.threshold.value-min) / range)*graphHeight); - g.drawLine(0,y2,(eRes-sRes)*av.charWidth,y2); - g2.setStroke(new BasicStroke()); - } - } - - public void drawBarGraph(Graphics g, AlignmentAnnotation aa, - int sRes, int eRes, - float min, float max, - int y) - { - if(sRes>aa.annotations.length) - return; - - eRes = Math.min(eRes, aa.annotations.length); - - int x=0, y1=y, y2=y; - - float range = max - min; - - if(min<0) - y2 = y - (int)((0-min / (range))*aa.graphHeight); - - g.setColor(Color.gray); - - g.drawLine(x,y2,(eRes-sRes)*av.charWidth,y2); - - int column; - int aaMax = aa.annotations.length-1; - - while( x < eRes-sRes ) - { - column = sRes + x; - if(av.hasHiddenColumns) - { - column = av.getColumnSelection().adjustForHiddenColumns(column); - } - - if(column > aaMax) - { - break; - } - - if (aa.annotations[column] == null) - { - x ++; - continue; - } - - g.setColor(aa.annotations[column].colour); - y1 = y - (int) (((aa.annotations[column].value-min) / (range)) * aa.graphHeight); - - if(y1-y2>0) - g.fillRect(x*av.charWidth, y2, av.charWidth, y1-y2 ); - else - g.fillRect(x*av.charWidth, y1, av.charWidth, y2-y1 ); - - x ++ ; - - } - if(aa.threshold!=null) - { - g.setColor(aa.threshold.colour); - Graphics2D g2 = (Graphics2D)g; - g2.setStroke(new BasicStroke(1, - BasicStroke.CAP_SQUARE, - BasicStroke.JOIN_ROUND, 3f, - new float[] { 5f, 3f }, 0f)); - - y2 = (int)(y - ((aa.threshold.value-min) / range)*aa.graphHeight); - g.drawLine(0,y2,(eRes-sRes)*av.charWidth,y2); - g2.setStroke(new BasicStroke()); - } - } - - // used by overview window - public void drawGraph(Graphics g, AlignmentAnnotation aa, int width, int y, int sRes, int eRes) - { - eRes = Math.min(eRes, aa.annotations.length); - g.setColor(Color.white); - g.fillRect(0, 0, width, y); - g.setColor(new Color(0, 0, 180)); - - int x = 0, height; - - for (int j = sRes; j < eRes; j++) - { - g.setColor(aa.annotations[j].colour); - - height = (int) ((aa.annotations[j].value / aa.graphMax) * y); - if(height>y) - height = y; - - g.fillRect(x, y - height, av.charWidth, height); - x += av.charWidth; - } - } - -} +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.gui; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.AdjustmentEvent; +import java.awt.event.AdjustmentListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.awt.image.BufferedImage; +import java.beans.PropertyChangeEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.Scrollable; +import javax.swing.ToolTipManager; + +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.Annotation; +import jalview.datamodel.ColumnSelection; +import jalview.datamodel.HiddenColumns; +import jalview.datamodel.SequenceI; +import jalview.gui.JalviewColourChooser.ColourChooserListener; +import jalview.renderer.AnnotationRenderer; +import jalview.renderer.AwtRenderPanelI; +import jalview.schemes.ResidueProperties; +import jalview.util.Comparison; +import jalview.util.MessageManager; +import jalview.util.Platform; +import jalview.viewmodel.ViewportListenerI; +import jalview.viewmodel.ViewportRanges; + +/** + * AnnotationPanel displays visible portion of annotation rows below unwrapped + * alignment + * + * @author $author$ + * @version $Revision$ + */ +public class AnnotationPanel extends JPanel implements AwtRenderPanelI, + MouseListener, MouseWheelListener, MouseMotionListener, + ActionListener, AdjustmentListener, Scrollable, ViewportListenerI +{ + enum DragMode + { + Select, Resize, Undefined + }; + + String HELIX = MessageManager.getString("label.helix"); + + String SHEET = MessageManager.getString("label.sheet"); + + /** + * For RNA secondary structure "stems" aka helices + */ + String STEM = MessageManager.getString("label.rna_helix"); + + String LABEL = MessageManager.getString("label.label"); + + String REMOVE = MessageManager.getString("label.remove_annotation"); + + String COLOUR = MessageManager.getString("action.colour"); + + public final Color HELIX_COLOUR = Color.red.darker(); + + public final Color SHEET_COLOUR = Color.green.darker().darker(); + + public final Color STEM_COLOUR = Color.blue.darker(); + + /** DOCUMENT ME!! */ + public AlignViewport av; + + AlignmentPanel ap; + + public int activeRow = -1; + + public BufferedImage image; + + public volatile BufferedImage fadedImage; + + // private Graphics2D gg; + + public FontMetrics fm; + + public int imgWidth = 0; + + boolean fastPaint = false; + + // Used For mouse Dragging and resizing graphs + int graphStretch = -1; + + int mouseDragLastX = -1; + + int mouseDragLastY = -1; + + DragMode dragMode = DragMode.Undefined; + + boolean mouseDragging = false; + + // for editing cursor + int cursorX = 0; + + int cursorY = 0; + + public final AnnotationRenderer renderer; + + private MouseWheelListener[] _mwl; + + /** + * Creates a new AnnotationPanel object. + * + * @param ap + * DOCUMENT ME! + */ + public AnnotationPanel(AlignmentPanel ap) + { + ToolTipManager.sharedInstance().registerComponent(this); + ToolTipManager.sharedInstance().setInitialDelay(0); + ToolTipManager.sharedInstance().setDismissDelay(10000); + this.ap = ap; + av = ap.av; + this.setLayout(null); + addMouseListener(this); + addMouseMotionListener(this); + ap.annotationScroller.getVerticalScrollBar() + .addAdjustmentListener(this); + // save any wheel listeners on the scroller, so we can propagate scroll + // events to them. + _mwl = ap.annotationScroller.getMouseWheelListeners(); + // and then set our own listener to consume all mousewheel events + ap.annotationScroller.addMouseWheelListener(this); + renderer = new AnnotationRenderer(); + + av.getRanges().addPropertyChangeListener(this); + } + + public AnnotationPanel(AlignViewport av) + { + this.av = av; + renderer = new AnnotationRenderer(); + } + + @Override + public void mouseWheelMoved(MouseWheelEvent e) + { + if (e.isShiftDown()) + { + e.consume(); + double wheelRotation = e.getPreciseWheelRotation(); + if (wheelRotation > 0) + { + av.getRanges().scrollRight(true); + } + else if (wheelRotation < 0) + { + av.getRanges().scrollRight(false); + } + } + else + { + // TODO: find the correct way to let the event bubble up to + // ap.annotationScroller + for (MouseWheelListener mwl : _mwl) + { + if (mwl != null) + { + mwl.mouseWheelMoved(e); + } + if (e.isConsumed()) + { + break; + } + } + } + } + + @Override + public Dimension getPreferredScrollableViewportSize() + { + Dimension ps = getPreferredSize(); + return new Dimension(ps.width, adjustForAlignFrame(false, ps.height)); + } + + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, + int orientation, int direction) + { + return 30; + } + + @Override + public boolean getScrollableTracksViewportHeight() + { + return false; + } + + @Override + public boolean getScrollableTracksViewportWidth() + { + return true; + } + + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, + int orientation, int direction) + { + return 30; + } + + /* + * (non-Javadoc) + * + * @see + * java.awt.event.AdjustmentListener#adjustmentValueChanged(java.awt.event + * .AdjustmentEvent) + */ + @Override + public void adjustmentValueChanged(AdjustmentEvent evt) + { + // update annotation label display + ap.getAlabels().setScrollOffset(-evt.getValue()); + } + + /** + * Calculates the height of the annotation displayed in the annotation panel. + * Callers should normally call the ap.adjustAnnotationHeight method to ensure + * all annotation associated components are updated correctly. + * + */ + public int adjustPanelHeight() + { + int height = av.calcPanelHeight(); + this.setPreferredSize(new Dimension(1, height)); + if (ap != null) + { + // revalidate only when the alignment panel is fully constructed + ap.validate(); + } + + return height; + } + + /** + * DOCUMENT ME! + * + * @param evt + * DOCUMENT ME! + */ + @Override + public void actionPerformed(ActionEvent evt) + { + AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation(); + if (aa == null) + { + return; + } + Annotation[] anot = aa[activeRow].annotations; + + if (anot.length < av.getColumnSelection().getMax()) + { + Annotation[] temp = new Annotation[av.getColumnSelection().getMax() + + 2]; + System.arraycopy(anot, 0, temp, 0, anot.length); + anot = temp; + aa[activeRow].annotations = anot; + } + + String action = evt.getActionCommand(); + if (action.equals(REMOVE)) + { + for (int index : av.getColumnSelection().getSelected()) + { + if (av.getAlignment().getHiddenColumns().isVisible(index)) + { + anot[index] = null; + } + } + } + else if (action.equals(LABEL)) + { + String exMesg = collectAnnotVals(anot, LABEL); + String label = JvOptionPane.showInputDialog( + MessageManager.getString("label.enter_label"), exMesg); + + if (label == null) + { + return; + } + + if ((label.length() > 0) && !aa[activeRow].hasText) + { + aa[activeRow].hasText = true; + } + + for (int index : av.getColumnSelection().getSelected()) + { + if (!av.getAlignment().getHiddenColumns().isVisible(index)) + { + continue; + } + + if (anot[index] == null) + { + anot[index] = new Annotation(label, "", ' ', 0); + } + else + { + anot[index].displayCharacter = label; + } + } + } + else if (action.equals(COLOUR)) + { + final Annotation[] fAnot = anot; + String title = MessageManager + .getString("label.select_foreground_colour"); + ColourChooserListener listener = new ColourChooserListener() + { + @Override + public void colourSelected(Color c) + { + HiddenColumns hiddenColumns = av.getAlignment() + .getHiddenColumns(); + for (int index : av.getColumnSelection().getSelected()) + { + if (hiddenColumns.isVisible(index)) + { + if (fAnot[index] == null) + { + fAnot[index] = new Annotation("", "", ' ', 0); + } + fAnot[index].colour = c; + } + } + }; + }; + JalviewColourChooser.showColourChooser(this, title, Color.black, + listener); + } + else + // HELIX, SHEET or STEM + { + char type = 0; + String symbol = "\u03B1"; // alpha + + if (action.equals(HELIX)) + { + type = 'H'; + } + else if (action.equals(SHEET)) + { + type = 'E'; + symbol = "\u03B2"; // beta + } + + // Added by LML to color stems + else if (action.equals(STEM)) + { + type = 'S'; + int column = av.getColumnSelection().getSelectedRanges().get(0)[0]; + symbol = aa[activeRow].getDefaultRnaHelixSymbol(column); + } + + if (!aa[activeRow].hasIcons) + { + aa[activeRow].hasIcons = true; + } + + String label = JvOptionPane.showInputDialog(MessageManager + .getString("label.enter_label_for_the_structure"), symbol); + + if (label == null) + { + return; + } + + if ((label.length() > 0) && !aa[activeRow].hasText) + { + aa[activeRow].hasText = true; + if (action.equals(STEM)) + { + aa[activeRow].showAllColLabels = true; + } + } + for (int index : av.getColumnSelection().getSelected()) + { + if (!av.getAlignment().getHiddenColumns().isVisible(index)) + { + continue; + } + + if (anot[index] == null) + { + anot[index] = new Annotation(label, "", type, 0); + } + + anot[index].secondaryStructure = type != 'S' ? type + : label.length() == 0 ? ' ' : label.charAt(0); + anot[index].displayCharacter = label; + + } + } + + av.getAlignment().validateAnnotation(aa[activeRow]); + ap.alignmentChanged(); + ap.alignFrame.setMenusForViewport(); + adjustPanelHeight(); + repaint(); + + return; + } + + /** + * Returns any existing annotation concatenated as a string. For each + * annotation, takes the description, if any, else the secondary structure + * character (if type is HELIX, SHEET or STEM), else the display character (if + * type is LABEL). + * + * @param anots + * @param type + * @return + */ + private String collectAnnotVals(Annotation[] anots, String type) + { + // TODO is this method wanted? why? 'last' is not used + + StringBuilder collatedInput = new StringBuilder(64); + String last = ""; + ColumnSelection viscols = av.getColumnSelection(); + HiddenColumns hidden = av.getAlignment().getHiddenColumns(); + + /* + * the selection list (read-only view) is in selection order, not + * column order; make a copy so we can sort it + */ + List selected = new ArrayList<>(viscols.getSelected()); + Collections.sort(selected); + for (int index : selected) + { + // always check for current display state - just in case + if (!hidden.isVisible(index)) + { + continue; + } + String tlabel = null; + if (anots[index] != null) + { // LML added stem code + if (type.equals(HELIX) || type.equals(SHEET) || type.equals(STEM) + || type.equals(LABEL)) + { + tlabel = anots[index].description; + if (tlabel == null || tlabel.length() < 1) + { + if (type.equals(HELIX) || type.equals(SHEET) + || type.equals(STEM)) + { + tlabel = "" + anots[index].secondaryStructure; + } + else + { + tlabel = "" + anots[index].displayCharacter; + } + } + } + if (tlabel != null && !tlabel.equals(last)) + { + if (last.length() > 0) + { + collatedInput.append(" "); + } + collatedInput.append(tlabel); + } + } + } + return collatedInput.toString(); + } + + /** + * Action on right mouse pressed on Mac is to show a pop-up menu for the + * annotation. Action on left mouse pressed is to find which annotation is + * pressed and mark the start of a column selection or graph resize operation. + * + * @param evt + */ + @Override + public void mousePressed(MouseEvent evt) + { + + AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation(); + if (aa == null) + { + return; + } + mouseDragLastX = evt.getX(); + mouseDragLastY = evt.getY(); + + /* + * add visible annotation heights until we reach the y + * position, to find which annotation it is in + */ + int height = 0; + activeRow = -1; + + final int y = evt.getY(); + for (int i = 0; i < aa.length; i++) + { + if (aa[i].visible) + { + height += aa[i].height; + } + + if (y < height) + { + if (aa[i].editable) + { + activeRow = i; + } + else if (aa[i].graph > 0) + { + /* + * we have clicked on a resizable graph annotation + */ + graphStretch = i; + } + break; + } + } + + /* + * isPopupTrigger fires in mousePressed on Mac, + * not until mouseRelease on Windows + */ + if (evt.isPopupTrigger() && activeRow != -1) + { + showPopupMenu(y, evt.getX()); + return; + } + + ap.getScalePanel().mousePressed(evt); + } + + /** + * Construct and display a context menu at the right-click position + * + * @param y + * @param x + */ + void showPopupMenu(final int y, int x) + { + if (av.getColumnSelection() == null + || av.getColumnSelection().isEmpty()) + { + return; + } + + JPopupMenu pop = new JPopupMenu( + MessageManager.getString("label.structure_type")); + JMenuItem item; + /* + * Just display the needed structure options + */ + if (av.getAlignment().isNucleotide()) + { + item = new JMenuItem(STEM); + item.addActionListener(this); + pop.add(item); + } + else + { + item = new JMenuItem(HELIX); + item.addActionListener(this); + pop.add(item); + item = new JMenuItem(SHEET); + item.addActionListener(this); + pop.add(item); + } + item = new JMenuItem(LABEL); + item.addActionListener(this); + pop.add(item); + item = new JMenuItem(COLOUR); + item.addActionListener(this); + pop.add(item); + item = new JMenuItem(REMOVE); + item.addActionListener(this); + pop.add(item); + pop.show(this, x, y); + } + + /** + * Action on mouse up is to clear mouse drag data and call mouseReleased on + * ScalePanel, to deal with defining the selection group (if any) defined by + * the mouse drag + * + * @param evt + */ + @Override + public void mouseReleased(MouseEvent evt) + { + graphStretch = -1; + mouseDragLastX = -1; + mouseDragLastY = -1; + mouseDragging = false; + if (dragMode == DragMode.Resize) + { + ap.adjustAnnotationHeight(); + } + dragMode = DragMode.Undefined; + ap.getScalePanel().mouseReleased(evt); + + /* + * isPopupTrigger is set in mouseReleased on Windows + * (in mousePressed on Mac) + */ + if (evt.isPopupTrigger() && activeRow != -1) + { + showPopupMenu(evt.getY(), evt.getX()); + } + + } + + /** + * DOCUMENT ME! + * + * @param evt + * DOCUMENT ME! + */ + @Override + public void mouseEntered(MouseEvent evt) + { + this.mouseDragging = false; + ap.getScalePanel().mouseEntered(evt); + } + + /** + * On leaving the panel, calls ScalePanel.mouseExited to deal with scrolling + * with column selection on a mouse drag + * + * @param evt + */ + @Override + public void mouseExited(MouseEvent evt) + { + ap.getScalePanel().mouseExited(evt); + } + + /** + * Action on starting or continuing a mouse drag. There are two possible + * actions: + *
    + *
  • drag up or down on a graphed annotation increases or decreases the + * height of the graph
  • + *
  • dragging left or right selects the columns dragged across
  • + *
+ * A drag on a graph annotation is treated as column selection if it starts + * with more horizontal than vertical movement, and as resize if it starts + * with more vertical than horizontal movement. Once started, the drag does + * not change mode. + * + * @param evt + */ + @Override + public void mouseDragged(MouseEvent evt) + { + /* + * if dragMode is Undefined: + * - set to Select if dx > dy + * - set to Resize if dy > dx + * - do nothing if dx == dy + */ + final int x = evt.getX(); + final int y = evt.getY(); + if (dragMode == DragMode.Undefined) + { + int dx = Math.abs(x - mouseDragLastX); + int dy = Math.abs(y - mouseDragLastY); + if (graphStretch == -1 || dx > dy) + { + /* + * mostly horizontal drag, or not a graph annotation + */ + dragMode = DragMode.Select; + } + else if (dy > dx) + { + /* + * mostly vertical drag + */ + dragMode = DragMode.Resize; + } + } + + if (dragMode == DragMode.Undefined) + { + /* + * drag is diagonal - defer deciding whether to + * treat as up/down or left/right + */ + return; + } + + try + { + if (dragMode == DragMode.Resize) + { + /* + * resize graph annotation if mouse was dragged up or down + */ + int deltaY = mouseDragLastY - evt.getY(); + if (deltaY != 0) + { + AlignmentAnnotation graphAnnotation = av.getAlignment() + .getAlignmentAnnotation()[graphStretch]; + int newHeight = Math.max(0, graphAnnotation.graphHeight + deltaY); + graphAnnotation.graphHeight = newHeight; + adjustPanelHeight(); + ap.paintAlignment(false, false); + } + } + else + { + /* + * for mouse drag left or right, delegate to + * ScalePanel to adjust the column selection + */ + ap.getScalePanel().mouseDragged(evt); + } + } finally + { + mouseDragLastX = x; + mouseDragLastY = y; + } + } + + /** + * Constructs the tooltip, and constructs and displays a status message, for + * the current mouse position + * + * @param evt + */ + @Override + public void mouseMoved(MouseEvent evt) + { + int yPos = evt.getY(); + AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation(); + + int row = getRowIndex(yPos, aa); + + if (row == -1) + { + this.setToolTipText(null); + return; + } + + int column = (evt.getX() / av.getCharWidth()) + + av.getRanges().getStartRes(); + column = Math.min(column, av.getRanges().getEndRes()); + + if (av.hasHiddenColumns()) + { + column = av.getAlignment().getHiddenColumns() + .visibleToAbsoluteColumn(column); + } + + AlignmentAnnotation ann = aa[row]; + if (row > -1 && ann.annotations != null + && column < ann.annotations.length) + { + String toolTip = buildToolTip(ann, column, aa); + setToolTipText(toolTip == null ? null + : JvSwingUtils.wrapTooltip(true, toolTip)); + String msg = getStatusMessage(av.getAlignment(), column, ann); + ap.alignFrame.setStatus(msg); + } + else + { + this.setToolTipText(null); + ap.alignFrame.setStatus(" "); + } + } + + /** + * Answers the index in the annotations array of the visible annotation at the + * given y position. This is done by adding the heights of visible annotations + * until the y position has been exceeded. Answers -1 if no annotations are + * visible, or the y position is below all annotations. + * + * @param yPos + * @param aa + * @return + */ + static int getRowIndex(int yPos, AlignmentAnnotation[] aa) + { + if (aa == null) + { + return -1; + } + int row = -1; + int height = 0; + + for (int i = 0; i < aa.length; i++) + { + if (aa[i].visible) + { + height += aa[i].height; + } + + if (height > yPos) + { + row = i; + break; + } + } + return row; + } + + /** + * Answers a tooltip for the annotation at the current mouse position, not + * wrapped in <html> tags (apply if wanted). Answers null if there is no + * tooltip to show. + * + * @param ann + * @param column + * @param anns + */ + static String buildToolTip(AlignmentAnnotation ann, int column, + AlignmentAnnotation[] anns) + { + String tooltip = null; + if (ann.graphGroup > -1) + { + StringBuilder tip = new StringBuilder(32); + boolean first = true; + for (int i = 0; i < anns.length; i++) + { + if (anns[i].graphGroup == ann.graphGroup + && anns[i].annotations[column] != null) + { + if (!first) + { + tip.append("
"); + } + first = false; + tip.append(anns[i].label); + String description = anns[i].annotations[column].description; + if (description != null && description.length() > 0) + { + tip.append(" ").append(description); + } + } + } + tooltip = first ? null : tip.toString(); + } + else if (column < ann.annotations.length + && ann.annotations[column] != null) + { + tooltip = ann.annotations[column].description; + } + + return tooltip; + } + + /** + * Constructs and returns the status bar message + * + * @param al + * @param column + * @param ann + */ + static String getStatusMessage(AlignmentI al, int column, + AlignmentAnnotation ann) + { + /* + * show alignment column and annotation description if any + */ + StringBuilder text = new StringBuilder(32); + text.append(MessageManager.getString("label.column")).append(" ") + .append(column + 1); + + if (column < ann.annotations.length && ann.annotations[column] != null) + { + String description = ann.annotations[column].description; + if (description != null && description.trim().length() > 0) + { + text.append(" ").append(description); + } + } + + /* + * if the annotation is sequence-specific, show the sequence number + * in the alignment, and (if not a gap) the residue and position + */ + SequenceI seqref = ann.sequenceRef; + if (seqref != null) + { + int seqIndex = al.findIndex(seqref); + if (seqIndex != -1) + { + text.append(", ").append(MessageManager.getString("label.sequence")) + .append(" ").append(seqIndex + 1); + char residue = seqref.getCharAt(column); + if (!Comparison.isGap(residue)) + { + text.append(" "); + String name; + if (al.isNucleotide()) + { + name = ResidueProperties.nucleotideName + .get(String.valueOf(residue)); + text.append(" Nucleotide: ") + .append(name != null ? name : residue); + } + else + { + name = 'X' == residue ? "X" + : ('*' == residue ? "STOP" + : ResidueProperties.aa2Triplet + .get(String.valueOf(residue))); + text.append(" Residue: ").append(name != null ? name : residue); + } + int residuePos = seqref.findPosition(column); + text.append(" (").append(residuePos).append(")"); + } + } + } + + return text.toString(); + } + + /** + * DOCUMENT ME! + * + * @param evt + * DOCUMENT ME! + */ + @Override + public void mouseClicked(MouseEvent evt) + { + // if (activeRow != -1) + // { + // AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation(); + // AlignmentAnnotation anot = aa[activeRow]; + // } + } + + // TODO mouseClicked-content and drawCursor are quite experimental! + public void drawCursor(Graphics graphics, SequenceI seq, int res, int x1, + int y1) + { + int pady = av.getCharHeight() / 5; + int charOffset = 0; + graphics.setColor(Color.black); + graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight()); + + if (av.validCharWidth) + { + graphics.setColor(Color.white); + + char s = seq.getCharAt(res); + + charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2; + graphics.drawString(String.valueOf(s), charOffset + x1, + (y1 + av.getCharHeight()) - pady); + } + + } + + private volatile boolean imageFresh = false; + + private Rectangle visibleRect = new Rectangle(), + clipBounds = new Rectangle(); + + /** + * DOCUMENT ME! + * + * @param g + * DOCUMENT ME! + */ + @Override + public void paintComponent(Graphics g) + { + + // BH: note that this method is generally recommended to + // call super.paintComponent(g). Otherwise, the children of this + // component will not be rendered. That is not needed here + // because AnnotationPanel does not have any children. It is + // just a JPanel contained in a JViewPort. + + computeVisibleRect(visibleRect); + + g.setColor(Color.white); + g.fillRect(0, 0, visibleRect.width, visibleRect.height); + + if (image != null) + { + // BH 2018 optimizing generation of new Rectangle(). + if (fastPaint + || (visibleRect.width != (clipBounds = g + .getClipBounds(clipBounds)).width) + || (visibleRect.height != clipBounds.height)) + { + + g.drawImage(image, 0, 0, this); + fastPaint = false; + return; + } + } + imgWidth = (av.getRanges().getEndRes() - av.getRanges().getStartRes() + + 1) * av.getCharWidth(); + if (imgWidth < 1) + { + return; + } + Graphics2D gg; + if (image == null || imgWidth != image.getWidth(this) + || image.getHeight(this) != getHeight()) + { + try + { + image = new BufferedImage(imgWidth, + ap.getAnnotationPanel().getHeight(), + BufferedImage.TYPE_INT_RGB); + } catch (OutOfMemoryError oom) + { + try + { + System.gc(); + } catch (Exception x) + { + } + ; + new OOMWarning( + "Couldn't allocate memory to redraw screen. Please restart Jalview", + oom); + return; + } + gg = (Graphics2D) image.getGraphics(); + + if (av.antiAlias) + { + gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + } + + gg.setFont(av.getFont()); + fm = gg.getFontMetrics(); + gg.setColor(Color.white); + gg.fillRect(0, 0, imgWidth, image.getHeight()); + imageFresh = true; + } + else + { + gg = (Graphics2D) image.getGraphics(); + + } + + drawComponent(gg, av.getRanges().getStartRes(), + av.getRanges().getEndRes() + 1); + gg.dispose(); + imageFresh = false; + g.drawImage(image, 0, 0, this); + } + + /** + * set true to enable redraw timing debug output on stderr + */ + private final boolean debugRedraw = false; + + /** + * non-Thread safe repaint + * + * @param horizontal + * repaint with horizontal shift in alignment + */ + public void fastPaint(int horizontal) + { + if ((horizontal == 0) || image == null + || av.getAlignment().getAlignmentAnnotation() == null + || av.getAlignment().getAlignmentAnnotation().length < 1 + || av.isCalcInProgress()) + { + repaint(); + return; + } + + int sr = av.getRanges().getStartRes(); + int er = av.getRanges().getEndRes() + 1; + int transX = 0; + + Graphics2D gg = (Graphics2D) image.getGraphics(); + + if (imgWidth>Math.abs(horizontal*av.getCharWidth())) { + //scroll is less than imgWidth away so can re-use buffered graphics + gg.copyArea(0, 0, imgWidth, getHeight(), + -horizontal * av.getCharWidth(), 0); + + if (horizontal > 0) // scrollbar pulled right, image to the left + { + transX = (er - sr - horizontal) * av.getCharWidth(); + sr = er - horizontal; + } + else if (horizontal < 0) + { + er = sr - horizontal; + } + } + gg.translate(transX, 0); + + drawComponent(gg, sr, er); + + gg.translate(-transX, 0); + + gg.dispose(); + + fastPaint = true; + + // Call repaint on alignment panel so that repaints from other alignment + // panel components can be aggregated. Otherwise performance of the overview + // window and others may be adversely affected. + av.getAlignPanel().repaint(); + } + + private volatile boolean lastImageGood = false; + + /** + * DOCUMENT ME! + * + * @param g + * DOCUMENT ME! + * @param startRes + * DOCUMENT ME! + * @param endRes + * DOCUMENT ME! + */ + public void drawComponent(Graphics g, int startRes, int endRes) + { + BufferedImage oldFaded = fadedImage; + if (av.isCalcInProgress()) + { + if (image == null) + { + lastImageGood = false; + return; + } + // We'll keep a record of the old image, + // and draw a faded image until the calculation + // has completed + if (lastImageGood + && (fadedImage == null || fadedImage.getWidth() != imgWidth + || fadedImage.getHeight() != image.getHeight())) + { + // System.err.println("redraw faded image ("+(fadedImage==null ? + // "null image" : "") + " lastGood="+lastImageGood+")"); + fadedImage = new BufferedImage(imgWidth, image.getHeight(), + BufferedImage.TYPE_INT_RGB); + + Graphics2D fadedG = (Graphics2D) fadedImage.getGraphics(); + + fadedG.setColor(Color.white); + fadedG.fillRect(0, 0, imgWidth, image.getHeight()); + + fadedG.setComposite( + AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .3f)); + fadedG.drawImage(image, 0, 0, this); + + } + // make sure we don't overwrite the last good faded image until all + // calculations have finished + lastImageGood = false; + + } + else + { + if (fadedImage != null) + { + oldFaded = fadedImage; + } + fadedImage = null; + } + + g.setColor(Color.white); + g.fillRect(0, 0, (endRes - startRes) * av.getCharWidth(), getHeight()); + + g.setFont(av.getFont()); + if (fm == null) + { + fm = g.getFontMetrics(); + } + + if ((av.getAlignment().getAlignmentAnnotation() == null) + || (av.getAlignment().getAlignmentAnnotation().length < 1)) + { + g.setColor(Color.white); + g.fillRect(0, 0, getWidth(), getHeight()); + g.setColor(Color.black); + if (av.validCharWidth) + { + g.drawString(MessageManager + .getString("label.alignment_has_no_annotations"), 20, 15); + } + + return; + } + lastImageGood = renderer.drawComponent(this, av, g, activeRow, startRes, + endRes); + if (!lastImageGood && fadedImage == null) + { + fadedImage = oldFaded; + } + } + + @Override + public FontMetrics getFontMetrics() + { + return fm; + } + + @Override + public Image getFadedImage() + { + return fadedImage; + } + + @Override + public int getFadedImageWidth() + { + return imgWidth; + } + + private int[] bounds = new int[2]; + + @Override + public int[] getVisibleVRange() + { + if (ap != null && ap.getAlabels() != null) + { + int sOffset = -ap.getAlabels().getScrollOffset(); + int visHeight = sOffset + ap.annotationSpaceFillerHolder.getHeight(); + bounds[0] = sOffset; + bounds[1] = visHeight; + return bounds; + } + else + { + return null; + } + } + + /** + * Try to ensure any references held are nulled + */ + public void dispose() + { + av = null; + ap = null; + image = null; + fadedImage = null; + // gg = null; + _mwl = null; + + /* + * I created the renderer so I will dispose of it + */ + if (renderer != null) + { + renderer.dispose(); + } + } + + @Override + public void propertyChange(PropertyChangeEvent evt) + { + // Respond to viewport range changes (e.g. alignment panel was scrolled) + // Both scrolling and resizing change viewport ranges: scrolling changes + // both start and end points, but resize only changes end values. + // Here we only want to fastpaint on a scroll, with resize using a normal + // paint, so scroll events are identified as changes to the horizontal or + // vertical start value. + if (evt.getPropertyName().equals(ViewportRanges.STARTRES)) + { + fastPaint((int) evt.getNewValue() - (int) evt.getOldValue()); + } + else if (evt.getPropertyName().equals(ViewportRanges.STARTRESANDSEQ)) + { + fastPaint(((int[]) evt.getNewValue())[0] + - ((int[]) evt.getOldValue())[0]); + } + else if (evt.getPropertyName().equals(ViewportRanges.MOVE_VIEWPORT)) + { + repaint(); + } + } + + /** + * computes the visible height of the annotation panel + * + * @param adjustPanelHeight + * - when false, just adjust existing height according to other + * windows + * @param annotationHeight + * @return height to use for the ScrollerPreferredVisibleSize + */ + public int adjustForAlignFrame(boolean adjustPanelHeight, + int annotationHeight) + { + /* + * Estimate available height in the AlignFrame for alignment + + * annotations. Deduct an estimate for title bar, menu bar, scale panel, + * hscroll, status bar, insets. + */ + int stuff = (ap.getViewName() != null ? 30 : 0) + + (Platform.isAMacAndNotJS() ? 120 : 140); + int availableHeight = ap.alignFrame.getHeight() - stuff; + int rowHeight = av.getCharHeight(); + + if (adjustPanelHeight) + { + int alignmentHeight = rowHeight * av.getAlignment().getHeight(); + + /* + * If not enough vertical space, maximize annotation height while keeping + * at least two rows of alignment visible + */ + if (annotationHeight + alignmentHeight > availableHeight) + { + annotationHeight = Math.min(annotationHeight, + availableHeight - 2 * rowHeight); + } + } + else + { + // maintain same window layout whilst updating sliders + annotationHeight = Math.min(ap.annotationScroller.getSize().height, + availableHeight - 2 * rowHeight); + } + return annotationHeight; + } +}