/* * 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 static jalview.util.ImageMaker.TYPE.EPS; import static jalview.util.ImageMaker.TYPE.PNG; import static jalview.util.ImageMaker.TYPE.SVG; import jalview.analysis.AnnotationSorter; import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; import jalview.bin.Cache; import jalview.datamodel.AlignmentI; import jalview.datamodel.HiddenColumns; import jalview.datamodel.SearchResultsI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.io.HTMLOutput; import jalview.jbgui.GAlignmentPanel; import jalview.math.AlignmentDimension; import jalview.schemes.ResidueProperties; import jalview.structure.StructureSelectionManager; import jalview.util.Comparison; import jalview.util.ImageMaker; import jalview.util.MessageManager; import jalview.viewmodel.ViewportListenerI; import jalview.viewmodel.ViewportRanges; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileWriter; import java.io.PrintWriter; import java.util.List; import javax.swing.SwingUtilities; /** * DOCUMENT ME! * * @author $author$ * @version $Revision: 1.161 $ */ public class AlignmentPanel extends GAlignmentPanel implements AdjustmentListener, Printable, AlignmentViewPanel, ViewportListenerI { public AlignViewport av; OverviewPanel overviewPanel; private SeqPanel seqPanel; private IdPanel idPanel; private boolean headless; IdwidthAdjuster idwidthAdjuster; /** DOCUMENT ME!! */ public AlignFrame alignFrame; private ScalePanel scalePanel; private AnnotationPanel annotationPanel; private AnnotationLabels alabels; private int hextent = 0; private int vextent = 0; /* * Flag set while scrolling to follow complementary cDNA/protein scroll. When * false, suppresses invoking the same method recursively. */ private boolean scrollComplementaryPanel = true; private PropertyChangeListener propertyChangeListener; private CalculationChooser calculationDialog; /** * Creates a new AlignmentPanel object. * * @param af * @param av */ public AlignmentPanel(AlignFrame af, final AlignViewport av) { alignFrame = af; this.av = av; setSeqPanel(new SeqPanel(av, this)); setIdPanel(new IdPanel(av, this)); setScalePanel(new ScalePanel(av, this)); idPanelHolder.add(getIdPanel(), BorderLayout.CENTER); idwidthAdjuster = new IdwidthAdjuster(this); idSpaceFillerPanel1.add(idwidthAdjuster, BorderLayout.CENTER); setAnnotationPanel(new AnnotationPanel(this)); setAlabels(new AnnotationLabels(this)); annotationScroller.setViewportView(getAnnotationPanel()); annotationSpaceFillerHolder.add(getAlabels(), BorderLayout.CENTER); scalePanelHolder.add(getScalePanel(), BorderLayout.CENTER); seqPanelHolder.add(getSeqPanel(), BorderLayout.CENTER); setScrollValues(0, 0); hscroll.addAdjustmentListener(this); vscroll.addAdjustmentListener(this); addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent evt) { // reset the viewport ranges when the alignment panel is resized // in particular, this initialises the end residue value when Jalview // is initialised ViewportRanges ranges = av.getRanges(); if (av.getWrapAlignment()) { int widthInRes = getSeqPanel().seqCanvas.getWrappedCanvasWidth( getSeqPanel().seqCanvas.getWidth()); ranges.setViewportWidth(widthInRes); } else { int widthInRes = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth(); int heightInSeq = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight(); ranges.setViewportWidth(widthInRes); ranges.setViewportHeight(heightInSeq); } } }); final AlignmentPanel ap = this; propertyChangeListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals("alignment")) { PaintRefresher.Refresh(ap, av.getSequenceSetId(), true, true); alignmentChanged(); } } }; av.addPropertyChangeListener(propertyChangeListener); av.getRanges().addPropertyChangeListener(this); fontChanged(); adjustAnnotationHeight(); updateLayout(); } @Override public AlignViewportI getAlignViewport() { return av; } public void alignmentChanged() { av.alignmentChanged(this); if (getCalculationDialog() != null) { getCalculationDialog().validateCalcTypes(); } alignFrame.updateEditMenuBar(); // no idea if we need to update structure paintAlignment(true, true); } /** * DOCUMENT ME! */ public void fontChanged() { // set idCanvas bufferedImage to null // to prevent drawing old image FontMetrics fm = getFontMetrics(av.getFont()); scalePanelHolder.setPreferredSize( new Dimension(10, av.getCharHeight() + fm.getDescent())); idSpaceFillerPanel1.setPreferredSize( new Dimension(10, av.getCharHeight() + fm.getDescent())); idwidthAdjuster.invalidate(); scalePanelHolder.invalidate(); getIdPanel().getIdCanvas().gg = null; getSeqPanel().seqCanvas.img = null; getAnnotationPanel().adjustPanelHeight(); Dimension d = calculateIdWidth(); d.setSize(d.width + 4, d.height); getIdPanel().getIdCanvas().setPreferredSize(d); hscrollFillerPanel.setPreferredSize(d); repaint(); } /** * Calculate the width of the alignment labels based on the displayed names * and any bounds on label width set in preferences. * * @return Dimension giving the maximum width of the alignment label panel * that should be used. */ public Dimension calculateIdWidth() { // calculate sensible default width when no preference is available Dimension r = null; if (av.getIdWidth() < 0) { int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300); int maxwidth = Math.max(20, Math.min(afwidth - 200, 2 * afwidth / 3)); r = calculateIdWidth(maxwidth); av.setIdWidth(r.width); } else { r = new Dimension(); r.width = av.getIdWidth(); r.height = 0; } return r; } /** * Calculate the width of the alignment labels based on the displayed names * and any bounds on label width set in preferences. * * @param maxwidth * -1 or maximum width allowed for IdWidth * @return Dimension giving the maximum width of the alignment label panel * that should be used. */ public Dimension calculateIdWidth(int maxwidth) { Container c = new Container(); FontMetrics fm = c.getFontMetrics( new Font(av.font.getName(), Font.ITALIC, av.font.getSize())); AlignmentI al = av.getAlignment(); int i = 0; int idWidth = 0; String id; while ((i < al.getHeight()) && (al.getSequenceAt(i) != null)) { SequenceI s = al.getSequenceAt(i); id = s.getDisplayId(av.getShowJVSuffix()); if (fm.stringWidth(id) > idWidth) { idWidth = fm.stringWidth(id); } i++; } // Also check annotation label widths i = 0; if (al.getAlignmentAnnotation() != null) { fm = c.getFontMetrics(getAlabels().getFont()); while (i < al.getAlignmentAnnotation().length) { String label = al.getAlignmentAnnotation()[i].label; if (fm.stringWidth(label) > idWidth) { idWidth = fm.stringWidth(label); } i++; } } return new Dimension( maxwidth < 0 ? idWidth : Math.min(maxwidth, idWidth), 12); } /** * Highlight the given results on the alignment * */ public void highlightSearchResults(SearchResultsI results) { boolean scrolled = scrollToPosition(results, 0, false); boolean noFastPaint = scrolled && av.getWrapAlignment(); getSeqPanel().seqCanvas.highlightSearchResults(results, noFastPaint); } /** * Scroll the view to show the position of the highlighted region in results * (if any) * * @param searchResults * @return */ public boolean scrollToPosition(SearchResultsI searchResults) { return scrollToPosition(searchResults, 0, false); } /** * Scrolls the view (if necessary) to show the position of the first * highlighted region in results (if any). Answers true if the view was * scrolled, or false if no matched region was found, or it is already * visible. * * @param results * @param verticalOffset * if greater than zero, allows scrolling to a position below the * first displayed sequence * @param centre * if true, try to centre the search results horizontally in the view * @return */ protected boolean scrollToPosition(SearchResultsI results, int verticalOffset, boolean centre) { int startv, endv, starts, ends; ViewportRanges ranges = av.getRanges(); if (results == null || results.isEmpty() || av == null || av.getAlignment() == null) { return false; } int seqIndex = av.getAlignment().findIndex(results); if (seqIndex == -1) { return false; } SequenceI seq = av.getAlignment().getSequenceAt(seqIndex); int[] r = results.getResults(seq, 0, av.getAlignment().getWidth()); if (r == null) { return false; } int start = r[0]; int end = r[1]; /* * To centre results, scroll to positions half the visible width * left/right of the start/end positions */ if (centre) { int offset = (ranges.getEndRes() - ranges.getStartRes() + 1) / 2 - 1; start = Math.max(start - offset, 0); end = end + offset - 1; } if (start < 0) { return false; } if (end == seq.getEnd()) { return false; } if (av.hasHiddenColumns()) { HiddenColumns hidden = av.getAlignment().getHiddenColumns(); start = hidden.absoluteToVisibleColumn(start); end = hidden.absoluteToVisibleColumn(end); if (start == end) { if (!hidden.isVisible(r[0])) { // don't scroll - position isn't visible return false; } } } /* * allow for offset of target sequence (actually scroll to one above it) */ seqIndex = Math.max(0, seqIndex - verticalOffset); boolean scrollNeeded = true; if (!av.getWrapAlignment()) { if ((startv = ranges.getStartRes()) >= start) { /* * Scroll left to make start of search results visible */ setScrollValues(start, seqIndex); } else if ((endv = ranges.getEndRes()) <= end) { /* * Scroll right to make end of search results visible */ setScrollValues(startv + end - endv, seqIndex); } else if ((starts = ranges.getStartSeq()) > seqIndex) { /* * Scroll up to make start of search results visible */ setScrollValues(ranges.getStartRes(), seqIndex); } else if ((ends = ranges.getEndSeq()) <= seqIndex) { /* * Scroll down to make end of search results visible */ setScrollValues(ranges.getStartRes(), starts + seqIndex - ends + 1); } /* * Else results are already visible - no need to scroll */ scrollNeeded = false; } else { scrollNeeded = ranges.scrollToWrappedVisible(start); } paintAlignment(false, false); return scrollNeeded; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public OverviewPanel getOverviewPanel() { return overviewPanel; } /** * DOCUMENT ME! * * @param op * DOCUMENT ME! */ public void setOverviewPanel(OverviewPanel op) { overviewPanel = op; } /** * * @param b * Hide or show annotation panel * */ public void setAnnotationVisible(boolean b) { if (!av.getWrapAlignment()) { annotationSpaceFillerHolder.setVisible(b); annotationScroller.setVisible(b); } repaint(); } /** * automatically adjust annotation panel height for new annotation whilst * ensuring the alignment is still visible. */ @Override public void adjustAnnotationHeight() { // TODO: display vertical annotation scrollbar if necessary // this is called after loading new annotation onto alignment if (alignFrame.getHeight() == 0) { System.out.println("NEEDS FIXING"); } validateAnnotationDimensions(true); addNotify(); // TODO: many places call this method and also paintAlignment with various // different settings. this means multiple redraws are triggered... paintAlignment(true, av.needToUpdateStructureViews()); } /** * calculate the annotation dimensions and refresh slider values accordingly. * need to do repaints/notifys afterwards. */ protected void validateAnnotationDimensions(boolean adjustPanelHeight) { int annotationHeight = getAnnotationPanel().adjustPanelHeight(); annotationHeight = getAnnotationPanel() .adjustForAlignFrame(adjustPanelHeight, annotationHeight); hscroll.addNotify(); annotationScroller.setPreferredSize( new Dimension(annotationScroller.getWidth(), annotationHeight)); Dimension e = idPanel.getSize(); alabels.setSize(new Dimension(e.width, annotationHeight)); annotationSpaceFillerHolder.setPreferredSize(new Dimension( annotationSpaceFillerHolder.getWidth(), annotationHeight)); annotationScroller.validate(); annotationScroller.addNotify(); } /** * update alignment layout for viewport settings * * @param wrap * DOCUMENT ME! */ public void updateLayout() { fontChanged(); setAnnotationVisible(av.isShowAnnotation()); boolean wrap = av.getWrapAlignment(); ViewportRanges ranges = av.getRanges(); ranges.setStartSeq(0); scalePanelHolder.setVisible(!wrap); hscroll.setVisible(!wrap); idwidthAdjuster.setVisible(!wrap); if (wrap) { annotationScroller.setVisible(false); annotationSpaceFillerHolder.setVisible(false); } else if (av.isShowAnnotation()) { annotationScroller.setVisible(true); annotationSpaceFillerHolder.setVisible(true); validateAnnotationDimensions(false); } int canvasWidth = getSeqPanel().seqCanvas.getWidth(); if (canvasWidth > 0) { // may not yet be laid out if (wrap) { int widthInRes = getSeqPanel().seqCanvas .getWrappedCanvasWidth(canvasWidth); ranges.setViewportWidth(widthInRes); } else { int widthInRes = (canvasWidth / av.getCharWidth()); int heightInSeq = (getSeqPanel().seqCanvas.getHeight() / av.getCharHeight()); ranges.setViewportWidth(widthInRes); ranges.setViewportHeight(heightInSeq); } } idSpaceFillerPanel1.setVisible(!wrap); repaint(); } /** * Adjust row/column scrollers to show a visible position in the alignment. * * @param x * visible column to scroll to * @param y * visible row to scroll to * */ public void setScrollValues(int xpos, int ypos) { int x = xpos; int y = ypos; if (av == null || av.getAlignment() == null) { return; } if (av.getWrapAlignment()) { setScrollingForWrappedPanel(x); } else { int width = av.getAlignment().getVisibleWidth(); int height = av.getAlignment().getHeight(); hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth(); vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight(); if (hextent > width) { hextent = width; } if (vextent > height) { vextent = height; } if ((hextent + x) > width) { x = width - hextent; } if ((vextent + y) > height) { y = height - vextent; } if (y < 0) { y = 0; } if (x < 0) { x = 0; } // update the scroll values hscroll.setValues(x, hextent, 0, width); vscroll.setValues(y, vextent, 0, height); } } /** * Respond to adjustment event when horizontal or vertical scrollbar is * changed * * @param evt * adjustment event encoding whether hscroll or vscroll changed */ @Override public void adjustmentValueChanged(AdjustmentEvent evt) { if (av.getWrapAlignment()) { adjustScrollingWrapped(evt); return; } ViewportRanges ranges = av.getRanges(); if (evt.getSource() == hscroll) { int oldX = ranges.getStartRes(); int oldwidth = ranges.getViewportWidth(); int x = hscroll.getValue(); int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth(); // if we're scrolling to the position we're already at, stop // this prevents infinite recursion of events when the scroll/viewport // ranges values are the same if ((x == oldX) && (width == oldwidth)) { return; } ranges.setViewportStartAndWidth(x, width); } else if (evt.getSource() == vscroll) { int oldY = ranges.getStartSeq(); int oldheight = ranges.getViewportHeight(); int y = vscroll.getValue(); int height = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight(); // if we're scrolling to the position we're already at, stop // this prevents infinite recursion of events when the scroll/viewport // ranges values are the same if ((y == oldY) && (height == oldheight)) { return; } ranges.setViewportStartAndHeight(y, height); } repaint(); } /** * Responds to a scroll change by setting the start position of the viewport. * Does * * @param evt */ protected void adjustScrollingWrapped(AdjustmentEvent evt) { if (evt.getSource() == hscroll) { return; // no horizontal scroll when wrapped } final ViewportRanges ranges = av.getRanges(); if (evt.getSource() == vscroll) { int newY = vscroll.getValue(); /* * if we're scrolling to the position we're already at, stop * this prevents infinite recursion of events when the scroll/viewport * ranges values are the same */ int oldX = ranges.getStartRes(); int oldY = ranges.getWrappedScrollPosition(oldX); if (oldY == newY) { return; } if (newY > -1) { /* * limit page up/down to one width's worth of positions */ int rowSize = ranges.getViewportWidth(); int newX = newY > oldY ? oldX + rowSize : oldX - rowSize; ranges.setViewportStartAndWidth(Math.max(0, newX), rowSize); } } else { // This is only called if file loaded is a jar file that // was wrapped when saved and user has wrap alignment true // as preference setting SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // When updating scrolling to use ViewportChange events, this code // could not be validated and it is not clear if it is now being // called. Log warning here in case it is called and unforeseen // problems occur Cache.log.warn( "Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences"); // scroll to start of panel ranges.setStartRes(0); ranges.setStartSeq(0); } }); } repaint(); } /* (non-Javadoc) * @see jalview.api.AlignmentViewPanel#paintAlignment(boolean) */ @Override public void paintAlignment(boolean updateOverview, boolean updateStructures) { final AnnotationSorter sorter = new AnnotationSorter(getAlignment(), av.isShowAutocalculatedAbove()); sorter.sort(getAlignment().getAlignmentAnnotation(), av.getSortAnnotationsBy()); repaint(); if (updateStructures) { av.getStructureSelectionManager().sequenceColoursChanged(this); } if (updateOverview) { if (overviewPanel != null) { overviewPanel.updateOverviewImage(); } } } /** * DOCUMENT ME! * * @param g * DOCUMENT ME! */ @Override public void paintComponent(Graphics g) { invalidate(); // needed so that the id width adjuster works correctly Dimension d = getIdPanel().getIdCanvas().getPreferredSize(); idPanelHolder.setPreferredSize(d); hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12)); validate(); // needed so that the id width adjuster works correctly /* * set scroll bar positions - tried to remove but necessary for split panel to resize correctly * though I still think this call should be elsewhere. */ ViewportRanges ranges = av.getRanges(); setScrollValues(ranges.getStartRes(), ranges.getStartSeq()); } /** * Set vertical scroll bar position, and number of increments, for wrapped * panel * * @param topLeftColumn * the column position at top left (0..) */ private void setScrollingForWrappedPanel(int topLeftColumn) { ViewportRanges ranges = av.getRanges(); int scrollPosition = ranges.getWrappedScrollPosition(topLeftColumn); int maxScroll = ranges.getWrappedMaxScroll(topLeftColumn); /* * a scrollbar's value can be set to at most (maximum-extent) * so we add extent (1) to the maxScroll value */ vscroll.setUnitIncrement(1); vscroll.setValues(scrollPosition, 1, 0, maxScroll + 1); } /** * Provides the implementation of Printable.print * * @param pg * DOCUMENT ME! * @param pf * DOCUMENT ME! * @param pi * DOCUMENT ME! * * @return DOCUMENT ME! * * @throws PrinterException */ @Override public int print(Graphics pg, PageFormat pf, int pi) throws PrinterException { pg.translate((int) pf.getImageableX(), (int) pf.getImageableY()); int pwidth = (int) pf.getImageableWidth(); int pheight = (int) pf.getImageableHeight(); if (av.getWrapAlignment()) { return printWrappedAlignment(pwidth, pheight, pi, pg, true); } else { return printUnwrapped(pwidth, pheight, pi, pg, pg); } } /** * Draws the alignment image, including sequence ids, sequences, and * annotation labels and annotations if shown, on either one or two Graphics * contexts. * * @param pageWidth * in pixels * @param pageHeight * in pixels * @param pageIndex * (0, 1, ...) * @param idGraphics * the graphics context for sequence ids and annotation labels * @param alignmentGraphics * the graphics context for sequences and annotations (may or may not * be the same context as idGraphics) * @return * @throws PrinterException */ public int printUnwrapped(int pageWidth, int pageHeight, int pageIndex, Graphics idGraphics, Graphics alignmentGraphics) throws PrinterException { final int idWidth = getVisibleIdWidth(false); /* * Get the horizontal offset to where we draw the sequences. * This is idWidth if using a single Graphics context, else zero. */ final int alignmentGraphicsOffset = idGraphics != alignmentGraphics ? 0 : idWidth; FontMetrics fm = getFontMetrics(av.getFont()); final int charHeight = av.getCharHeight(); final int scaleHeight = charHeight + fm.getDescent(); idGraphics.setColor(Color.white); idGraphics.fillRect(0, 0, pageWidth, pageHeight); idGraphics.setFont(av.getFont()); /* * How many sequences and residues can we fit on a printable page? */ final int totalRes = (pageWidth - idWidth) / av.getCharWidth(); final int totalSeq = (pageHeight - scaleHeight) / charHeight - 1; final int alignmentWidth = av.getAlignment().getVisibleWidth(); int pagesWide = (alignmentWidth / totalRes) + 1; final int startRes = (pageIndex % pagesWide) * totalRes; final int endRes = Math.min(startRes + totalRes - 1, alignmentWidth - 1); final int startSeq = (pageIndex / pagesWide) * totalSeq; final int alignmentHeight = av.getAlignment().getHeight(); final int endSeq = Math.min(startSeq + totalSeq, alignmentHeight); int pagesHigh = ((alignmentHeight / totalSeq) + 1) * pageHeight; if (av.isShowAnnotation()) { pagesHigh += getAnnotationPanel().adjustPanelHeight() + 3; } pagesHigh /= pageHeight; if (pageIndex >= (pagesWide * pagesHigh)) { return Printable.NO_SUCH_PAGE; } final int alignmentDrawnHeight = (endSeq - startSeq) * charHeight + 3; /* * draw the Scale at horizontal offset, then reset to top left (0, 0) */ alignmentGraphics.translate(alignmentGraphicsOffset, 0); getScalePanel().drawScale(alignmentGraphics, startRes, endRes, pageWidth - idWidth, scaleHeight); alignmentGraphics.translate(-alignmentGraphicsOffset, 0); /* * Draw the sequence ids, offset for scale height, * then reset to top left (0, 0) */ idGraphics.translate(0, scaleHeight); IdCanvas idCanvas = getIdPanel().getIdCanvas(); List selection = av.getSelectionGroup() == null ? null : av.getSelectionGroup().getSequences(null); idCanvas.drawIds((Graphics2D) idGraphics, av, startSeq, endSeq - 1, selection); idGraphics.setFont(av.getFont()); idGraphics.translate(0, -scaleHeight); /* * draw the sequences, offset for scale height, and id width (if using a * single graphics context), then reset to (0, scale height) */ alignmentGraphics.translate(alignmentGraphicsOffset, scaleHeight); getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics, startRes, endRes, startSeq, endSeq - 1); alignmentGraphics.translate(-alignmentGraphicsOffset, 0); if (av.isShowAnnotation() && (endSeq == alignmentHeight)) { /* * draw annotation labels; drawComponent() translates by * getScrollOffset(), so compensate for that first; * then reset to (0, scale height) */ int offset = getAlabels().getScrollOffset(); idGraphics.translate(0, -offset); idGraphics.translate(0, alignmentDrawnHeight); getAlabels().drawComponent(idGraphics, idWidth); idGraphics.translate(0, -alignmentDrawnHeight); /* * draw the annotations starting at * (idOffset, alignmentHeight) from (0, scaleHeight) */ alignmentGraphics.translate(alignmentGraphicsOffset, alignmentDrawnHeight); getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av, alignmentGraphics, -1, startRes, endRes + 1); /* * reset to left margin */ alignmentGraphics.translate(-alignmentGraphicsOffset, 0); } return Printable.PAGE_EXISTS; } /** * Prints one page of an alignment in wrapped mode. Returns * Printable.PAGE_EXISTS (0) if a page was drawn, or Printable.NO_SUCH_PAGE if * no page could be drawn (page number out of range). *

* The method is to write the whole alignment, but set a clip region such that * only the specified page is written. This allows specified page(s) to be * printed from the print dialog. The whole image may be written simply by * making the page size match the image size. In this case, parameter * {@code clipToPage} should be set to {@code false}, so that more output (for * example the second panel of a split frame) can be written if wanted. * * @param pageWidth * @param pageHeight * @param pageNumber * (0, 1, ...) * @param g * @param clipToPage * * @return */ public int printWrappedAlignment(int pageWidth, int pageHeight, int pageNumber, Graphics g, boolean clipToPage) { int annotationHeight = 0; if (av.isShowAnnotation()) { annotationHeight = getAnnotationPanel().adjustPanelHeight(); } int hgap = av.getCharHeight(); if (av.getScaleAboveWrapped()) { hgap += av.getCharHeight(); } int cHeight = av.getAlignment().getHeight() * av.getCharHeight() + hgap + annotationHeight; int idWidth = getVisibleIdWidth(false); int maxwidth = av.getAlignment().getVisibleWidth(); int resWidth = getSeqPanel().seqCanvas .getWrappedCanvasWidth(pageWidth - idWidth); int totalHeight = cHeight * (maxwidth / resWidth + 1); g.setColor(Color.white); g.fillRect(0, 0, pageWidth, pageHeight); g.setFont(av.getFont()); g.setColor(Color.black); /* * method: print the whole wrapped alignment, but with a clip region that * is restricted to the requested page; this supports selective print of * single pages or ranges, (at the cost of some repeated processing in * the 'normal' case, when all pages are printed) */ g.translate(0, -pageNumber * pageHeight); if (clipToPage) { g.setClip(0, pageNumber * pageHeight, pageWidth, pageHeight); } /* * draw sequence ids and annotation labels (if shown) */ IdCanvas idCanvas = getIdPanel().getIdCanvas(); idCanvas.drawIdsWrapped((Graphics2D) g, av, 0, totalHeight); g.translate(idWidth, 0); getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(g, pageWidth - idWidth, totalHeight, 0); g.translate(-idWidth, 0); if ((pageNumber * pageHeight) < totalHeight) { return Printable.PAGE_EXISTS; } else { return Printable.NO_SUCH_PAGE; } } /** * get current sequence ID panel width, or nominal value if panel were to be * displayed using default settings * * @return */ public int getVisibleIdWidth() { return getVisibleIdWidth(true); } /** * get current sequence ID panel width, or nominal value if panel were to be * displayed using default settings * * @param onscreen * indicate if the Id width for onscreen or offscreen display should * be returned * @return */ public int getVisibleIdWidth(boolean onscreen) { // see if rendering offscreen - check preferences and calc width accordingly if (!onscreen && Cache.getDefault("FIGURE_AUTOIDWIDTH", false)) { return calculateIdWidth(-1).width + 4; } Integer idwidth = null; if (onscreen || (idwidth = Cache .getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null) { int w = getIdPanel().getWidth(); return (w > 0 ? w : calculateIdWidth().width + 4); } return idwidth.intValue() + 4; } /** * Generates an image of the alignment panel of the specified type. If * {@code type} is not null, the image is written to the file, otherwise the * user is prompted to specify the output file before writing to it. * * @param type * @param file * @param forSplitFrame */ void makeAlignmentImage(ImageMaker.TYPE type, File file, boolean forSplitFrame) { int borderBottomOffset = 5; long pSessionId = System.currentTimeMillis(); headless = (System.getProperty("java.awt.headless") != null && System.getProperty("java.awt.headless").equals("true")); if (alignFrame != null && !headless) { if (file != null) { alignFrame.setProgressBar(MessageManager .formatMessage("status.saving_file", new Object[] { type.getLabel() }), pSessionId); } } try { /* * if exporting a split frame image, the graphics object has * width: maximum of the top and bottom image widths * height: sum of the top and bottom image heights */ AlignmentPanel comp = null; AlignmentDimension dim1 = getAlignmentDimension(); AlignmentDimension dim2 = new AlignmentDimension(0, 0); if (forSplitFrame) { comp = ((AlignViewport) av.getCodingComplement()) .getAlignPanel(); dim2 = comp.getAlignmentDimension(); } final int graphicsHeight = dim1.height + dim2.height + borderBottomOffset; final int graphicsWidth = Math.max(dim1.width, dim2.width); final String dialogTitle = MessageManager .formatMessage("label.make_alignment_image", type.getName()); String imageTitle = type == PNG ? null : alignFrame.getTitle(); ImageMaker im = new ImageMaker(this, type, dialogTitle, graphicsWidth, graphicsHeight, file, imageTitle, alignFrame, pSessionId, headless); Graphics graphics = im.getGraphics(); if (graphics == null) { return; } graphics.setColor(Color.white); graphics.fillRect(0, 0, graphicsWidth, graphicsHeight); if (av.getWrapAlignment()) { printWrappedAlignment(dim1.width, dim1.height + borderBottomOffset, 0, graphics, false); } else { printUnwrapped(dim1.width, dim1.height, 0, graphics, graphics); } if (forSplitFrame) { /* * append coding complement image */ boolean test = true; if (test) { /* * debug location of next write to Graphics */ graphics.setColor(Color.red); graphics.drawString("Hello world", 0, 0); graphics.setColor(Color.black); } if (av.getCodingComplement().getWrapAlignment()) { comp.printWrappedAlignment(dim2.width, dim2.height + borderBottomOffset, 0, graphics, false); } else { comp.printUnwrapped(dim2.width, dim2.height, 0, graphics, graphics); } } im.writeImage(); } catch (OutOfMemoryError err) { // Be noisy here. System.out.println("########################\n" + "OUT OF MEMORY " + file + "\n" + "########################"); new OOMWarning("Creating Image for " + file, err); // System.out.println("Create IMAGE: " + err); } catch (Exception ex) { ex.printStackTrace(); } } /** * Computes and answers the width and height of the alignment in pixels, * including *

* The alignment may be in wrapped or unwrapped mode. *