//----------------------------------------------------------------------------//
// //
// N e s t V i e w //
// //
//----------------------------------------------------------------------------//
// <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.glyph.ui;
import omr.constant.Constant;
import omr.constant.ConstantSet;
import omr.graph.DigraphView;
import omr.glyph.Nest;
import omr.glyph.facets.Glyph;
import omr.lag.Lag;
import omr.lag.Section;
import omr.score.entity.PartNode;
import omr.score.ui.PaintingParameters;
import omr.text.FontInfo;
import omr.text.TextChar;
import omr.text.TextLine;
import omr.text.TextWord;
import omr.ui.Colors;
import omr.ui.util.UIUtil;
import omr.ui.view.RubberPanel;
import omr.util.WeakPropertyChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Class {@code NestView} is a view that combines the display of
* several lags to represent a nest of glyphs.
*
* @author Hervé Bitteur
*/
public class NestView
extends RubberPanel
implements DigraphView, PropertyChangeListener
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(NestView.class);
//~ Instance fields --------------------------------------------------------
/** The underlying nest */
protected final Nest nest;
/** Related glyphs controller */
protected final GlyphsController controller;
/** The sequence of lags */
protected final List<Lag> lags;
/** Additional items rendering */
protected final List<ItemRenderer> itemRenderers = new ArrayList<>();
//~ Constructors -----------------------------------------------------------
//----------//
// NestView //
//----------//
/**
* Create a nest view.
*
* @param nest the underlying nest of glyphs
* @param controller the related glyphs controller
* @param lags the various lags to be displayed
*/
public NestView (Nest nest,
GlyphsController controller,
List<Lag> lags)
{
this.nest = nest;
this.controller = controller;
this.lags = lags;
setName(nest.getName() + "-View");
setBackground(Color.white);
// (Weakly) listening on ViewParameters and PaintingParameters
PropertyChangeListener listener = new WeakPropertyChangeListener(this);
ViewParameters.getInstance()
.addPropertyChangeListener(listener);
PaintingParameters.getInstance()
.addPropertyChangeListener(listener);
}
//~ Methods ----------------------------------------------------------------
//-----------------//
// addItemRenderer //
//-----------------//
/**
* Register an items renderer to renderAttachments items.
*
* @param renderer the additional renderer
*/
public void addItemRenderer (ItemRenderer renderer)
{
itemRenderers.add(new WeakItemRenderer(renderer));
}
//---------------//
// getController //
//---------------//
public GlyphsController getController ()
{
return controller;
}
//----------------//
// propertyChange //
//----------------//
@Override
public void propertyChange (PropertyChangeEvent evt)
{
// Whatever the property change, we simply repaint the view
repaint();
}
//---------//
// refresh //
//---------//
@Override
public void refresh ()
{
repaint();
}
//--------//
// render //
//--------//
/**
* Render the nest in the provided Graphics context, which may be
* already scaled.
*
* @param g the graphics context
*/
@Override
public void render (Graphics2D g)
{
// Should we draw the section borders?
final boolean drawBorders = ViewParameters.getInstance().isSectionMode();
// Stroke for borders
final Stroke oldStroke = UIUtil.setAbsoluteStroke(g, 1f);
if (lags != null) {
for (Lag lag : lags) {
// Render all sections, using the colors they have been assigned
for (Section section : lag.getVertices()) {
section.render(g, drawBorders);
}
}
}
// Paint additional items, such as recognized items, etc...
renderItems(g);
// Restore stroke
g.setStroke(oldStroke);
}
//-----------------//
// renderGlyphArea //
//-----------------//
/**
* Render the box area of a glyph, using inverted color.
*
* @param glyph the glyph whose area is to be rendered
* @param g the graphic context
*/
protected void renderGlyphArea (Glyph glyph,
Graphics2D g)
{
// Check the clipping
Rectangle box = glyph.getBounds();
if ((box != null) && box.intersects(g.getClipBounds())) {
g.fillRect(box.x, box.y, box.width, box.height);
}
}
//-------------//
// renderItems //
//-------------//
/**
* Room for rendering additional items, on top of the basic nest
* itself.
* This default implementation paints the selected glyph set,
* or the selected sections set, if any.
*
* @param g the graphic context
*/
protected void renderItems (Graphics2D g)
{
// Additional renderers if any
for (ItemRenderer renderer : itemRenderers) {
renderer.renderItems(g);
}
// Render the selected glyph(s) if any
Set<Glyph> glyphs = nest.getSelectedGlyphSet();
if (glyphs != null) {
// Decorations first
Stroke oldStroke = UIUtil.setAbsoluteStroke(g, 1f);
g.setColor(Color.blue);
for (Glyph glyph : glyphs) {
// Draw character boxes for textual glyphs?
if (glyph.isText()) {
if (ViewParameters.getInstance()
.isLetterBoxPainting()) {
TextWord word = glyph.getTextWord();
if (word != null) {
for (TextChar ch : word.getChars()) {
Rectangle b = ch.getBounds();
g.drawRect(b.x, b.y, b.width, b.height);
}
}
}
}
// Draw attachments, if any
glyph.renderAttachments(g);
// Draw glyph line?
if (ViewParameters.getInstance().isLinePainting()) {
glyph.renderLine(g);
}
}
g.setStroke(oldStroke);
}
// Glyph areas second, using XOR mode for the area
if (!ViewParameters.getInstance().isSectionMode()) {
// Glyph selection mode
if (glyphs != null) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setColor(Color.black);
g2.setXORMode(Color.darkGray);
for (Glyph glyph : glyphs) {
renderGlyphArea(glyph, g2);
}
g2.dispose();
// Display words of a sentence, if any
if (ViewParameters.getInstance().isSentencePainting()) {
for (Glyph glyph : glyphs) {
renderGlyphSentence(glyph, g);
}
}
// Display translation links, if any
if (ViewParameters.getInstance().isTranslationPainting()) {
for (Glyph glyph : glyphs) {
renderGlyphTranslations(glyph, g);
}
}
}
} else {
// Section selection mode
for (Lag lag : lags) {
Set<Section> selected = lag.getSelectedSectionSet();
if ((selected != null) && !selected.isEmpty()) {
for (Section section : selected) {
section.renderSelected(g);
}
}
}
}
}
//-------------------------//
// renderGlyphTranslations //
//-------------------------//
private void renderGlyphTranslations (Glyph glyph,
Graphics2D g)
{
if (glyph.getTranslations().isEmpty()) {
return;
}
Stroke oldStroke = UIUtil.setAbsoluteStroke(g, 1f);
Color oldColor = g.getColor();
g.setColor(Colors.TRANSLATION_LINK);
// Compute end radius, with fixed size whatever the current zoom
double r = 1 / g.getTransform().getScaleX();
for (PartNode node : glyph.getTranslations()) {
for (Line2D line : node.getTranslationLinks(glyph)) {
// Draw line
g.draw(line);
// Draw ending points
Ellipse2D e1 = new Ellipse2D.Double(
line.getX1() - r, line.getY1() - r, 2 * r, 2 * r);
g.draw(e1);
Ellipse2D e2 = new Ellipse2D.Double(
line.getX2() - r, line.getY2() - r, 2 * r, 2 * r);
g.draw(e2);
}
}
g.setColor(oldColor);
g.setStroke(oldStroke);
}
//---------------------//
// renderGlyphSentence //
//---------------------//
/**
* Display the relation between the glyph/word at hand and the other
* words of the same containing sentence
*
* @param glyph the provided selected glyph
* @param g graphic context
*/
private void renderGlyphSentence (Glyph glyph,
Graphics2D g)
{
if (glyph.getTextWord() == null) {
return;
}
TextLine sentence = glyph.getTextWord().getTextLine();
Color oldColor = g.getColor();
if (constants.showSentenceBaseline.isSet()) {
// Display the whole sentence baseline
g.setColor(Colors.SENTENCE_BASELINE);
Stroke oldStroke = UIUtil.setAbsoluteStroke(g, 1f);
Path2D path = new Path2D.Double();
TextWord prevWord = null;
for (TextWord word : sentence.getWords()) {
Point2D left = word.getBaseline().getP1();
if (prevWord == null) {
path.moveTo(left.getX(), left.getY());
} else {
path.lineTo(left.getX(), left.getY());
}
Point2D right = word.getBaseline().getP2();
path.lineTo(right.getX(), right.getY());
prevWord = word;
}
g.draw(path);
g.setStroke(oldStroke);
} else {
// Display a x-height rectangle between words
g.setColor(Colors.SENTENCE_GAPS);
FontInfo font = sentence.getMeanFont();
double height = font.pointsize * 0.4f; // TODO: Explain this 0.4
TextWord prevWord = null;
for (TextWord word : sentence.getWords()) {
if (prevWord != null) {
Path2D path = new Path2D.Double();
Point2D from = prevWord.getBaseline().getP2();
path.moveTo(from.getX(), from.getY());
path.lineTo(from.getX(), from.getY() - height);
Point2D to = word.getBaseline().getP1();
path.lineTo(to.getX(), to.getY() - height);
path.lineTo(to.getX(), to.getY());
path.closePath();
g.fill(path);
}
prevWord = word;
}
}
g.setColor(oldColor);
}
//~ Inner Interfaces -------------------------------------------------------
//--------------//
// ItemRenderer //
//--------------//
/**
* Used to plug additional items renderers to this view.
*/
public static interface ItemRenderer
{
//~ Methods ------------------------------------------------------------
void renderItems (Graphics2D g);
}
//------------------//
// WeakItemRenderer //
//------------------//
private static class WeakItemRenderer
implements ItemRenderer
{
protected final WeakReference<ItemRenderer> weakRenderer;
public WeakItemRenderer (ItemRenderer renderer)
{
weakRenderer = new WeakReference<>(renderer);
}
@Override
public void renderItems (Graphics2D g)
{
ItemRenderer renderer = weakRenderer.get();
if (renderer != null) {
renderer.renderItems(g);
}
}
}
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Constant.Boolean showSentenceBaseline = new Constant.Boolean(
true,
"Should we show sentence baseline (vs inter-word gaps)?");
}
}