/*
* 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
* If shift is pressed, then scrolling is left or right instead, and is * delegated to AlignmentPanel, so that both sequences and annotations are * scrolled together. *
* This object is a MouseWheelListener to AnnotationLabels, so mouse wheel * events over the labels are delegated to this method. *
* Note that this method may also be fired by scrolling with a gesture on a
* trackpad.
*/
@Override
public void mouseWheelMoved(MouseWheelEvent e)
{
if (e.isShiftDown())
{
ap.getSeqPanel().mouseWheelMoved(e);
}
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(this,
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))
{
Color col = JColorChooser.showDialog(this,
MessageManager.getString("label.select_foreground_colour"),
Color.black);
for (int index : av.getColumnSelection().getSelected())
{
if (!av.getAlignment().getHiddenColumns().isVisible(index))
{
continue;
}
if (anot[index] == null)
{
anot[index] = new Annotation("", "", ' ', 0);
}
anot[index].colour = col;
}
}
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
");
}
}
if (tip.length() != 6)
{
tip.setLength(tip.length() - 4);
tooltip = tip.toString() + "";
}
}
else if (column < ann.annotations.length
&& ann.annotations[column] != null)
{
String description = ann.annotations[column].description;
if (description != null && description.length() > 0)
{
tooltip = JvSwingUtils.wrapTooltip(true, description);
}
else
{
tooltip = null; // no tooltip if null or empty description
}
}
else
{
// clear the tooltip.
tooltip = null;
}
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;
/**
* DOCUMENT ME!
*
* @param g
* DOCUMENT ME!
*/
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(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.getRanges().getEndRes() - av.getRanges().getStartRes()
+ 1) * av.getCharWidth();
if (imgWidth < 1)
{
return;
}
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;
}
drawComponent(gg, av.getRanges().getStartRes(),
av.getRanges().getEndRes() + 1);
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) || gg == 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;
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);
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.isAMac() ? 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;
}
}