//----------------------------------------------------------------------------// // // // P a g e P a i n t e r // // // //----------------------------------------------------------------------------// // <editor-fold defaultstate="collapsed" desc="hdr"> // // Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. // // This software is released under the GNU General Public License. // // Goto http://kenai.com/projects/audiveris to report bugs or suggestions. // //----------------------------------------------------------------------------// // </editor-fold> package omr.score.ui; import omr.constant.Constant; import omr.constant.ConstantSet; import omr.glyph.Shape; import static omr.glyph.Shape.*; import omr.glyph.facets.Glyph; import omr.math.GeoUtil; import omr.score.Score; import omr.score.entity.AbstractNotation; import omr.score.entity.Arpeggiate; import omr.score.entity.Articulation; import omr.score.entity.Beam; import omr.score.entity.Chord; import omr.score.entity.Clef; import omr.score.entity.Coda; import omr.score.entity.Dynamics; import omr.score.entity.Fermata; import omr.score.entity.KeySignature; import omr.score.entity.LyricsItem; import omr.score.entity.MeasureElement; import omr.score.entity.Note; import omr.score.entity.Ornament; import omr.score.entity.Pedal; import omr.score.entity.ScoreSystem; import omr.score.entity.Segno; import omr.score.entity.Slur; import omr.score.entity.Staff; import omr.score.entity.SystemPart; import omr.score.entity.Text; import omr.score.entity.TimeSignature; import omr.score.entity.TimeSignature.InvalidTimeSignature; import omr.score.entity.Tuplet; import omr.score.entity.Voice; import omr.score.entity.Wedge; import omr.score.visitor.AbstractScoreVisitor; import omr.sheet.Scale; import omr.sheet.SystemInfo; import omr.text.FontInfo; import omr.text.TextLine; import omr.text.TextWord; import omr.ui.symbol.Alignment; import static omr.ui.symbol.Alignment.*; import static omr.ui.symbol.Alignment.Horizontal.*; import static omr.ui.symbol.Alignment.Vertical.*; import omr.ui.symbol.MusicFont; import omr.ui.symbol.OmrFont; import omr.ui.symbol.ShapeSymbol; import omr.ui.symbol.Symbols; import static omr.ui.symbol.Symbols.*; import omr.ui.symbol.TextFont; import omr.ui.util.UIUtil; import omr.util.HorizontalSide; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.util.ConcurrentModificationException; import java.util.LinkedHashSet; import java.util.Set; /** * Class {@code PagePainter} is an abstract class that defines common * features of a page painter. * <p>It is specialized by: <ul> * <li>{@link PagePhysicalPainter} for the presentation of page entities over * the sheet glyphs</li> * <li>We used to also have a PageLogicalPainter for the "ideal" score view</li> * * @author Hervé Bitteur */ public abstract class PagePainter extends AbstractScoreVisitor { //~ Static fields/initializers --------------------------------------------- /** Specific application parameters */ private static final Constants constants = new Constants(); /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(PagePainter.class); /** The alignment used by default */ protected static final Alignment defaultAlignment = AREA_CENTER; /** A transformation to half scale (used for slot time annotation) */ protected static final AffineTransform halfAT = AffineTransform. getScaleInstance( 0.5, 0.5); /** Font for annotations */ protected static final Font basicFont = new Font( "Sans Serif", Font.PLAIN, constants.basicFontSize.getValue()); /** Abscissa offset, in pixels, for annotation near system */ protected static final int annotationDx = 15; /** Ordinate offset, in pixels, for annotation near staff or system */ protected static final int annotationDy = 15; // Painting parameters protected static final PaintingParameters parameters = PaintingParameters. getInstance(); /** Sequence of colors for voices. TODO: Choose better colors, with alpha! */ private static final int alpha = 150; private static final Color[] voiceColors = new Color[]{ /** Cyan */ new Color(0, 255, 255, alpha), /** Orange */ new Color(255, 200, 0, alpha), /** Pink */ new Color(255, 175, 175, alpha), /** Green */ new Color(0, 255, 0, alpha), /** Magenta */ new Color(255, 0, 255, alpha), /** Blue */ new Color(0, 0, 255, alpha), /** Yellow */ new Color(255, 255, 0, alpha) }; //~ Instance fields -------------------------------------------------------- /** Clipping area */ protected final Rectangle oldClip; /** Flag for painting staff lines */ protected final boolean linePainting; // Graphic context protected final Graphics2D g; // Global color protected final Color defaultColor; // Painting voices with different colors? protected final boolean coloredVoices; // Should we draw annotations? protected final boolean annotated; // Related score protected Score score; // Specific font for music symbols protected MusicFont musicFont; // Global scale protected Scale scale; // For staff lines protected int lineThickness; protected Stroke lineStroke; // For stems protected float stemThickness; protected float stemHalfThickness; protected Stroke stemStroke; // For beams protected float beamThickness; protected float beamHalfThickness; // The system being currently painted protected ScoreSystem system; protected SystemInfo systemInfo; //~ Constructors ----------------------------------------------------------- //--------------// // PagePainter // //--------------// /** * Creates a new PagePainter object. * * @param graphics Graphic context * @param color the default color * @param coloredVoices true for voices with different colors * @param linePainting true for painting staff lines * @param annotated true if annotations are to be drawn */ public PagePainter (Graphics graphics, Color color, boolean coloredVoices, boolean linePainting, boolean annotated) { g = (Graphics2D) graphics.create(); oldClip = g.getClipBounds(); this.defaultColor = color; this.coloredVoices = coloredVoices; this.linePainting = linePainting; this.annotated = annotated; // Use a specific color for all score entities g.setColor(color); // Anti-aliasing g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Default font for annotations g.setFont(basicFont); } //~ Methods ---------------------------------------------------------------- //------------------// // visit Arpeggiate // //------------------// @Override public boolean visit (Arpeggiate arpeggiate) { try { // Voice color? handleVoiceColor(arpeggiate); // Draw an arpeggiate symbol with proper height // Using half-height of arpeggiate character as the elementary unit // We need clipping to draw half characters final Rectangle box = arpeggiate.getBox(); box.height -= 2; // Gives better results // How many half arpeggiate symbols do we need? final int halfHeight = scale.getInterline(); final int count = (int) Math.rint( (double) box.height / halfHeight); final TextLayout layout = musicFont.layout(ARPEGGIATO); final Point start = new Point(box.x, box.y + box.height); // Draw count * half symbols, bottom up for (int i = 0; i < count; i++) { // Define a clipping area final Rectangle area = new Rectangle( start.x, start.y - halfHeight, box.width, halfHeight); area.grow(6, 6); // Add some margin to avoid gaps final Rectangle clip = oldClip.intersection(area); // Anything to draw in the clipping area? if ((clip.width > 0) && (clip.height > 0)) { g.setClip(clip); layout.draw(g, start.x, start.y); } // Move up half height start.y -= halfHeight; } // Restore oldClip g.setClip(oldClip); } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + arpeggiate, ex); } return true; } //--------------------// // visit Articulation // //--------------------// @Override public boolean visit (Articulation articulation) { return visit((MeasureElement) articulation); } //------------// // visit Beam // //------------// @Override public boolean visit (Beam beam) { try { final Point left = new Point( beam.getPoint(HorizontalSide.LEFT)); final Point right = new Point( beam.getPoint(HorizontalSide.RIGHT)); final int dx = (int) Math.rint(stemHalfThickness); final int dy = (int) Math.rint(beamHalfThickness); // Compute precise abscissae values if (beam.isHook()) { // Just a hook stuck to a stem on one side if (!beam.getChords().isEmpty()) { Chord chord = beam.getChords().get(0); if (chord.getCenter().x < beam.getCenter().x) { left.x -= dx; } else { right.x += dx; } } else { // beam.addError( // beam.getGlyphs().iterator().next(), // "Beam hook with no related chord"); return false; } } else { // Standard beam stuck to 2 stems, one on either side left.x -= dx; right.x += dx; } // Use a filled polygon to paint the beam final Polygon polygon = new Polygon(); polygon.addPoint(left.x, left.y - dy); polygon.addPoint(left.x, left.y + dy); polygon.addPoint(right.x, right.y + dy); polygon.addPoint(right.x, right.y - dy); // Use related voices (if already set) Set<Voice> voices = new LinkedHashSet<>(); for (Chord chord : beam.getChords()) { Voice voice = chord.getVoice(); if (voice != null) { voices.add(voice); } } if (!voices.isEmpty()) { // Paint all colors, one on top of the other for (Voice voice : voices) { g.setColor(colorOf(voice)); g.fill(polygon); } } else { // Paint with default color g.setColor(defaultColor); g.fill(polygon); } } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + beam, ex); } return true; } //-------------// // visit Chord // //-------------// @Override public boolean visit (Chord chord) { try { // Voice indication ? handleVoiceColor(chord); // Flags ? final int fn = chord.getFlagsNumber(); if (fn > 0) { Point tail = chord.getTailLocation(); Point head = chord.getHeadLocation(); // We draw from tail boolean goesUp = head.y < tail.y; paint( Chord.getFlagShape(fn, goesUp), location(tail, chord), goesUp ? BOTTOM_LEFT : TOP_LEFT); } } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + chord, ex); } return true; } //------------// // visit Clef // //------------// @Override public boolean visit (Clef clef) { try { paint(clef.getShape(), clef.getReferencePoint()); } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + clef, ex); } return true; } //------------// // visit Coda // //------------// @Override public boolean visit (Coda coda) { return visit((MeasureElement) coda); } //----------------// // visit Dynamics // //----------------// @Override public boolean visit (Dynamics dynamics) { return visit((MeasureElement) dynamics); } //---------------// // visit Fermata // //---------------// @Override public boolean visit (Fermata fermata) { return visit((MeasureElement) fermata); } //--------------------// // visit KeySignature // //--------------------// @Override public boolean visit (KeySignature keySignature) { try { final Staff staff = keySignature.getStaff(); final Shape clefKind = keySignature.getClefKind(); final int key = keySignature.getKey(); final int sign = Integer.signum(key); final Shape shape = (key < 0) ? FLAT : SHARP; final TextLayout layout = musicFont.layout(shape); final Rectangle box = keySignature.getBox(); final int unitDx = getKeySigItemDx(); if (box == null) { ///logger.warn("Null box for " + keySignature); ///keySignature.addError("Null box for " + keySignature); return false; } // Flats : use vertical stick on left // Sharps : use center of the two vertical sticks final Alignment alignment = new Alignment( BASELINE, (key < 0) ? LEFT : CENTER); Point point = new Point(box.x, 0); for (int i = 1; i <= (key * sign); i++) { int n = i * sign; double pitch = KeySignature.getItemPosition(n, clefKind); Integer ref = keySignature.getItemPixelAbscissa(n); if (ref != null) { ///logger.info(n + ":" + ref + " for " + keySignature); point = new Point(ref, 0); } paint(layout, location(point, staff, pitch), alignment); point.x += unitDx; // Fall-back if ref is not known } } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + keySignature, ex); } return true; } //----------------------// // visit MeasureElement // //----------------------// @Override public boolean visit (MeasureElement measureElement) { handleVoiceColor(measureElement); try { if (measureElement.getShape() != null) { try { paint( musicFont.layout( measureElement.getShape(), measureElement.getDimension()), measureElement.getReferencePoint()); } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn("Cannot paint " + measureElement, ex); } } } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + measureElement, ex); } return true; } //------------// // visit Note // //------------// @Override public boolean visit (Note note) { try { final Chord chord = note.getChord(); final Glyph stem = chord.getStem(); final Shape shape = note.getShape(); final Point center = note.getCenter(); // Note head if (stem != null) { // Note is attached to a stem, link note display to the stem paint(shape, noteLocation(note), (center.x < chord.getTailLocation().x) ? MIDDLE_RIGHT : MIDDLE_LEFT); } else { // Standard display paint(shape.getPhysicalShape(), noteLocation(note)); } // Accidental ? final Glyph accid = note.getAccidental(); if (accid != null) { paint(accid.getShape(), accidentalLocation(note, accid), BASELINE_CENTER); } } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + note, ex); } return true; } //----------------// // visit Ornament // //----------------// @Override public boolean visit (Ornament ornament) { return visit((MeasureElement) ornament); } //-------------// // visit Pedal // //-------------// @Override public boolean visit (Pedal pedal) { return visit((MeasureElement) pedal); } //-------------// // visit Segno // //-------------// @Override public boolean visit (Segno segno) { return visit((MeasureElement) segno); } //------------// // visit Slur // //------------// @Override public boolean visit (Slur slur) { if (coloredVoices) { g.setColor(defaultColor); Voice voice = null; if (slur.isTie()) { Note note = slur.getLeftNote(); if (note != null) { Chord chord = note.getChord(); voice = chord.getVoice(); } note = slur.getRightNote(); if (note != null) { Chord chord = note.getChord(); if (voice == null) { voice = chord.getVoice(); } else if ((chord.getVoice() != null) && (chord.getVoice() != voice)) { ///slur.addError("Tie with different voices"); } } if (voice != null) { g.setColor(colorOf(voice)); } } } try { Stroke oldStroke = g.getStroke(); g.setStroke(lineStroke); g.draw(slur.getCurve()); g.setStroke(oldStroke); } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + slur, ex); } return true; } //------------------// // visit SystemPart // //------------------// @Override public boolean visit (SystemPart part) { g.setColor(defaultColor); try { // We don't draw dummy parts? if (part.isDummy()) { return false; } // Draw a brace/bracket? if (part.getBrace() != null) { if (part.getBrace().getShape() == Shape.BRACE) { // We have nice half braces in MusicalSymbols font final Rectangle box = braceBox(part); final Point center = GeoUtil.centerOf(box); final Dimension halfDim = new Dimension( box.width, box.height / 2); paint( musicFont.layout(SYMBOL_BRACE_UPPER_HALF, halfDim), center, BOTTOM_CENTER); paint( musicFont.layout(SYMBOL_BRACE_LOWER_HALF, halfDim), center, TOP_CENTER); } else if (part.getBrace().getShape() == Shape.BRACKET) { TextLayout trunk = musicFont.layout(Shape.THICK_BARLINE); double width = trunk.getBounds().getWidth(); double barWidth = 0.5 * part.getScale().getInterline(); AffineTransform at = AffineTransform.getScaleInstance(barWidth / width, 1); final Line2D line = bracketLine(part); Point topLeft = new Point( (int) Math.rint(line.getX1() - barWidth / 2), (int) Math.rint(line.getY1())); Point botLeft = new Point( (int) Math.rint(line.getX2() - barWidth / 2), (int) Math.rint(line.getY2())); TextLayout upper = musicFont.layout(SYMBOL_BRACKET_UPPER_SERIF.getString(), at); TextLayout lower = musicFont.layout(SYMBOL_BRACKET_LOWER_SERIF.getString(), at); paint(upper, topLeft, BASELINE_LEFT); paint(lower, botLeft, BASELINE_LEFT); BarPainter barPainter = BarPainter.getBarPainter(Shape.THICK_BARLINE); barPainter.draw(g, line.getP1(), line.getP2(), part); } } // Render the part starting barline, if any if (part.getStartingBarline() != null) { part.getStartingBarline().accept(this); } } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + part, ex); } return true; } //--------------// // visit Tuplet // //--------------// @Override public boolean visit (Tuplet tuplet) { return visit((MeasureElement) tuplet); } //------------// // visit Text // //------------// @Override public boolean visit (Text text) { try { g.setColor(defaultColor); TextLine sentence = text.getSentence(); if (sentence.getWords().isEmpty()) { // This may occur with obsolete Text, linked to old sentence // When painting between TEXTS and PAGES steps return false; } if (text instanceof LyricsItem) { // Just print the single LyricsItem word (and not the full line) paintWord(((LyricsItem) text).getWord()); } else { // Print the whole line for (TextWord word : sentence.getWords()) { paintWord(word); } } } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + text, ex); } return true; } //------------------// // handleVoiceColor // //------------------// private void handleVoiceColor (MeasureElement measureElement) { if (coloredVoices) { g.setColor(defaultColor); if (measureElement instanceof AbstractNotation) { handleVoiceColor(measureElement.getChord()); } } } //------------------// // handleVoiceColor // //------------------// private void handleVoiceColor (Chord chord) { if (coloredVoices) { g.setColor(defaultColor); if (chord != null) { Voice voice = chord.getVoice(); if (voice != null) { g.setColor(colorOf(voice)); } } } } //-----------// // paintWord // //-----------// private void paintWord (TextWord word) { FontInfo meanFont = word.getTextLine().getMeanFont(); if (meanFont != null) { Font font = new TextFont(meanFont); FontRenderContext frc = g.getFontRenderContext(); TextLayout layout = new TextLayout(word.getValue(), font, frc); paint(layout, word.getLocation(), BASELINE_LEFT); } } //---------------------// // visit TimeSignature // //---------------------// @Override public boolean visit (TimeSignature timeSignature) { g.setColor(defaultColor); try { final Shape shape = timeSignature.getShape(); final Point center = timeSignature.getCenter(); final Staff staff = timeSignature.getStaff(); final int dy = staff.pitchToPixels(-2); if (shape == Shape.NO_LEGAL_TIME) { // If this is an illegal shape, do not draw anything. // TODO: we could draw a special sign for this return false; } // Special symbol? if ((shape == COMMON_TIME) || (shape == CUT_TIME)) { paint(shape, center); } else { // Paint numerator paintTimeNumber( timeSignature.getNumerator(), new Point(center.x, center.y - dy)); // Paint denominator paintTimeNumber( timeSignature.getDenominator(), new Point(center.x, center.y + dy)); } } catch (InvalidTimeSignature ex) { logger.warn("Invalid time signature", ex); } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { // timeSignature.addError( // timeSignature.getGlyphs().iterator().next(), // "Error painting timeSignature " + ex); } return true; } //-------------// // visit Wedge // //-------------// @Override public boolean visit (Wedge wedge) { g.setColor(defaultColor); try { if (wedge.isStart()) { final Rectangle box = wedge.getGlyph().getBounds(); Point single; Point top; Point bot; if (wedge.getShape() == Shape.CRESCENDO) { single = new Point(box.x, box.y + (box.height / 2)); top = new Point(box.x + box.width, box.y); bot = new Point(box.x + box.width, box.y + box.height); } else { single = new Point( box.x + box.width, box.y + (box.height / 2)); top = new Point(box.x, box.y); bot = new Point(box.x, box.y + box.height); } paintLine(single, top); paintLine(single, bot); } } catch (ConcurrentModificationException ignored) { } catch (Exception ex) { logger.warn( getClass().getSimpleName() + " Error visiting " + wedge, ex); } return true; } //--------------------// // accidentalLocation // //--------------------// /** * Report the precise location to be used for a given accidental sigh * (with respect to the related note) * * @param note the related note * @param accidental the accidental glyph * @return the location to be used for painting */ protected abstract Point accidentalLocation (Note note, Glyph accidental); //----------// // braceBox // //----------// /** * Report the precise box to be used for a given brace * * @param part the related part * @return the box to be used for painting */ protected abstract Rectangle braceBox (SystemPart part); //-------------// // bracketLine // //-------------// /** * Report the vertical driving line (barline) to be used for a * given bracket * * @param part the related part * @return the line to drive the painting */ protected abstract Line2D bracketLine (SystemPart part); //--------------// // noteLocation // //--------------// /** * Report the precise location to be used for a given note * * @param note the related note * @return the location to be used for painting */ protected abstract Point noteLocation (Note note); //-------------// // basicLayout // //-------------// /** * Build a TextLayout from a String of BasicFont characters * (transformed by the provided AffineTransform if any) * * @param str the string of proper codes * @param fat potential affine transformation * @return the (sized) TextLayout ready to be drawn */ protected TextLayout basicLayout (String str, AffineTransform fat) { FontRenderContext frc = g.getFontRenderContext(); Font font = (fat == null) ? basicFont : basicFont.deriveFont(fat); return new TextLayout(str, font, frc); } //-----------------// // getKeySigItemDx // //-----------------// /** * Report the theoretical abscissa gap between items of one key signature * (the individual sharp or flat signs) * * @return the theoretical dx between items */ protected int getKeySigItemDx () { return scale.toPixels(constants.keySigItemDx); } //----------------// // initParameters // //----------------// /** * Initialization sequence common to ScorePhysicalPainter and * ScoreLogicalPainter */ protected void initParameters () { // Determine staff lines parameters lineThickness = scale.getMainFore(); lineStroke = new BasicStroke( lineThickness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); // Determine stems parameters stemThickness = scale.getMainFore(); stemHalfThickness = stemThickness / 2; stemStroke = new BasicStroke( stemThickness, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); // Set stroke for lines if (linePainting) { g.setStroke(lineStroke); } else { UIUtil.setAbsoluteStroke(g, 1f); } } //----------// // location // //----------// /** * Build the desired absolute drawing point, the abscissa being adjusted to * fit on the provided chord stem, and the ordinate being computed from the * pitch position with respect to the containing staff * * @param sysPoint the (approximate) system-based drawing point * @param chord the chord whose stem must be stuck to the (note) symbol * @param staff the containing staff * @param pitch the pitch position with respect to the staff * @return the Point, as precise as possible in X & Y */ protected Point location (Point sysPoint, Chord chord, Staff staff, double pitch) { return new Point( preciseAbscissa(sysPoint, chord), staff.getTopLeft().y + staff.pitchToPixels(pitch)); } //----------// // location // //----------// /** * Build the desired absolute drawing point, the abscissa being adjusted to * fit on the provided chord stem * * @param sysPoint the (approximate) system-based drawing point * @param chord the chord whose stem must be stuck to the (note) symbol * @return the Point, as precise as possible in X */ protected Point location (Point sysPoint, Chord chord) { return new Point(preciseAbscissa(sysPoint, chord), sysPoint.y); } //----------// // location // //----------// /** * Build the desired absolute drawing point, the ordinate being computed * from the pitch position with respect to the containing staff * * @param sysPoint the (approximate) system-based drawing point * @param staff the containing staff * @param pitch the pitch position with respect to the staff * @return the Point, as precise as possible in Y */ protected Point location (Point sysPoint, Staff staff, double pitch) { return new Point( sysPoint.x, staff.getTopLeft().y + staff.pitchToPixels(pitch)); } //-------// // paint // //-------// /** * This is the general paint method for drawing a symbol layout, at a * specified location, using a specified alignment * * @param layout what: the symbol, perhaps transformed * @param location where: the precise location in the display * @param alignment how: the way the symbol is aligned wrt the location */ protected void paint (TextLayout layout, Point location, Alignment alignment) { OmrFont.paint(g, layout, location, alignment); } //-------// // paint // //-------// /** * A convenient painting method, using default alignment * (CENTER + MIDDLE) * * @param layout what: the symbol, perhaps transformed * @param location where: the precise location in the display */ protected void paint (TextLayout layout, Point location) { if (layout != null) { paint(layout, location, defaultAlignment); } else { logger.debug("null layout"); } } //-------// // paint // //-------// /** * Paint a symbol * * @param shape the symbol shape * @param location the precise location * @param alignment alignment wrt the symbol */ protected void paint (Shape shape, Point location, Alignment alignment) { ShapeSymbol symbol = Symbols.getSymbol(shape); if (symbol != null) { symbol.paintSymbol(g, musicFont, location, alignment); } } //-------// // paint // //-------// /** * Paint a symbol with default alignment * * @param shape the symbol shape * @param location the precise location */ protected void paint (Shape shape, Point location) { paint(shape, location, defaultAlignment); } //-----------// // paintLine // //-----------// /** * Draw a line from one Point to another Point * * @param from first point * @param to second point */ protected void paintLine (Point from, Point to) { if ((from != null) && (to != null)) { g.drawLine(from.x, from.y, to.x, to.y); } else { logger.warn("line not painted due to null reference"); } } //-----------------// // paintTimeNumber // //-----------------// /** * Paint a (time) number using the coordinates in units of its center point * within the containing system part * * @param number the number whose icon must be painted * @param center the center of desired location */ protected void paintTimeNumber (int number, Point center) { int[] codes = ShapeSymbol.numberCodes(number); String str = new String(codes, 0, codes.length); MusicFont.paint(g, musicFont.layout(str), center, AREA_CENTER); } //-----------------// // preciseAbscissa // //-----------------// /** * Compute the rather precise abscissa, adjacent to the provided chord stem, * on the side implied by the specified approximate sysPoint * * @param sysPoint the (note) approximate center * @param chord the chord/stem the note should be stuck to * @return the precise value for x */ protected int preciseAbscissa (Point sysPoint, Chord chord) { // Compute symbol abscissa according to chord stem int stemX = chord.getTailLocation().x; double dx = stemHalfThickness - 2d; // slight adjustment if (sysPoint.x < stemX) { // Symbol is on left side of stem return (int) (stemX - dx); } else { // Symbol is on right side of stem return (int) (stemX + dx); } } //---------// // colorOf // //---------// /** * Report the color to use when painting elements related to the provided * voice * * @param voice the provided voice * @return the color to use */ private Color colorOf (Voice voice) { if (coloredVoices) { // Use table of colors, circularly. int index = (voice.getId() - 1) % voiceColors.length; return voiceColors[index]; } else { return defaultColor; } } //~ Inner Classes ---------------------------------------------------------- //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { //~ Instance fields ---------------------------------------------------- /** Font for annotations */ Constant.Integer basicFontSize = new Constant.Integer( "points", 30, "Standard font size for annotations"); /** dx between items in a key signature */ final Scale.Fraction keySigItemDx = new Scale.Fraction( 1.1, "dx between items in a key signature"); } }