//----------------------------------------------------------------------------//
// //
// E v a l u a t i o n B o a r d //
// //
//----------------------------------------------------------------------------//
// <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.glyph.Evaluation;
import omr.glyph.GlyphNetwork;
import omr.glyph.Glyphs;
import omr.glyph.Shape;
import omr.glyph.ShapeEvaluator;
import omr.glyph.facets.Glyph;
import omr.selection.GlyphEvent;
import omr.selection.MouseMovement;
import omr.selection.SelectionHint;
import omr.selection.UserEvent;
import omr.sheet.Sheet;
import omr.sheet.SystemInfo;
import omr.ui.Board;
import omr.ui.Colors;
import omr.ui.util.Panel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.forms.layout.FormSpecs;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
/**
* Class {@code EvaluationBoard} is a board dedicated to the display of
* evaluation results performed by an evaluator.
* It also provides through buttons the ability to manually assign a shape to
* the glyph at hand.
*
* @author Hervé Bitteur
* @author Brenton Partridge
*/
class EvaluationBoard
extends Board
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(
EvaluationBoard.class);
/** Events this board is interested in */
private static final Class<?>[] eventsRead = new Class<?>[]{GlyphEvent.class};
/** Color for well recognized glyphs */
private static final Color EVAL_GOOD_COLOR = new Color(100, 200, 100);
/** Color for hardly recognized glyphs */
private static final Color EVAL_SOSO_COLOR = new Color(150, 150, 150);
//~ Instance fields --------------------------------------------------------
/** The evaluator this display is related to */
private final ShapeEvaluator evaluator = GlyphNetwork.getInstance();
/** Related glyphs controller */
private final GlyphsController glyphsController;
/** Related sheet */
private final Sheet sheet;
/** Pane for detailed info display about the glyph evaluation */
private final Selector selector;
/** Do we use GlyphChecker annotations? */
private boolean useAnnotations;
//~ Constructors -----------------------------------------------------------
//-----------------//
// EvaluationBoard //
//-----------------//
/**
* Create a simplified passive evaluation board with one neural
* network evaluator.
*
* @param glyphModel the related glyph model
*/
public EvaluationBoard (GlyphsController glyphModel,
boolean expanded)
{
this(null, glyphModel, expanded);
useAnnotations = false;
}
//-----------------//
// EvaluationBoard //
//-----------------//
/**
* Create an evaluation board with one neural network evaluator
* and the ability to force glyph shape.
*
* @param glyphController the related glyph controller
* @param sheet the related sheet, or null
*/
public EvaluationBoard (Sheet sheet,
GlyphsController glyphController,
boolean expanded)
{
super(
Board.EVAL,
glyphController.getNest().getGlyphService(),
eventsRead,
false,
expanded);
this.glyphsController = glyphController;
this.sheet = sheet;
selector = new Selector();
defineLayout();
useAnnotations = true;
}
//~ Methods ----------------------------------------------------------------
//----------//
// evaluate //
//----------//
/**
* Evaluate the glyph at hand, and display the result in the
* evaluator dedicated area.
*
* @param glyph the glyph at hand
*/
public void evaluate (Glyph glyph)
{
if ((glyph == null)
|| (glyph.getShape() == Shape.STEM)
|| glyph.isBar()) {
// Blank the output
selector.setEvals(null, null);
} else {
if (evaluator != null) {
if (useAnnotations) {
SystemInfo system = sheet.getSystemOf(glyph);
if (system != null) {
selector.setEvals(
evaluator.evaluate(
glyph,
system,
selector.evalCount(),
constants.minGrade.getValue(),
EnumSet.of(ShapeEvaluator.Condition.CHECKED),
null),
glyph);
}
} else {
selector.setEvals(
evaluator.evaluate(
glyph,
null,
selector.evalCount(),
constants.minGrade.getValue(),
ShapeEvaluator.NO_CONDITIONS,
null),
glyph);
}
}
}
}
//---------//
// onEvent //
//---------//
/**
* Call-back triggered when Glyph Selection has been modified.
*
* @param event the (Glyph) Selection
*/
@Override
public void onEvent (UserEvent event)
{
try {
// Ignore RELEASING
if (event.movement == MouseMovement.RELEASING) {
return;
}
// Don't evaluate Added glyph, since this would hide Compound evaluation
if (event.hint == SelectionHint.LOCATION_ADD) {
return;
}
if (event instanceof GlyphEvent) {
GlyphEvent glyphEvent = (GlyphEvent) event;
Glyph glyph = glyphEvent.getData();
evaluate(glyph);
}
} catch (Exception ex) {
logger.warn(sheet.getLogPrefix()
+ getClass().getName() + " output error", ex);
}
}
//--------------//
// defineLayout //
//--------------//
private void defineLayout ()
{
String colSpec = Panel.makeColumns(3);
FormLayout layout = new FormLayout(colSpec, "");
int visibleButtons = Math.min(
constants.visibleButtons.getValue(),
selector.buttons.size());
for (int i = 0; i < visibleButtons; i++) {
layout.appendRow(FormSpecs.PREF_ROWSPEC);
}
///SystemUtils.IS_OS_WINDOWS
// Uncomment following line to have fixed sized rows, whether
// they are filled or not
///layout.setRowGroups(new int[][]{{1, 3, 4, 5 }});
PanelBuilder builder = new PanelBuilder(layout, getBody());
builder.setDefaultDialogBorder();
CellConstraints cst = new CellConstraints();
for (int i = 0; i < visibleButtons; i++) {
int r = i + 1; // --------------------------------
EvalButton evb = selector.buttons.get(i);
builder.add(evb.grade, cst.xy(5, r));
builder.add(
(sheet != null) ? evb.button : evb.field,
cst.xyw(7, r, 5));
}
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Evaluation.Grade minGrade = new Evaluation.Grade(
0.0,
"Threshold on displayable grade");
Constant.Integer visibleButtons = new Constant.Integer(
"buttons",
5,
"Max number of buttons in the shape selector");
}
//------------//
// EvalButton //
//------------//
private class EvalButton
implements ActionListener
{
//~ Instance fields ----------------------------------------------------
// Shape button or text field. Only one of them will be created and used
final JButton button;
final JLabel field;
// The related grade
JLabel grade = new JLabel("", SwingConstants.RIGHT);
//~ Constructors -------------------------------------------------------
public EvalButton ()
{
grade.setToolTipText("Grade of the evaluation");
if (sheet != null) {
button = new JButton();
button.addActionListener(this);
button.setToolTipText("Assignable shape");
button.setHorizontalAlignment(SwingConstants.LEFT);
field = null;
} else {
field = new JLabel();
field.setHorizontalAlignment(JTextField.CENTER);
field.setToolTipText("Evaluated shape");
field.setHorizontalAlignment(SwingConstants.LEFT);
button = null;
}
}
//~ Methods ------------------------------------------------------------
// Triggered by button
@Override
public void actionPerformed (ActionEvent e)
{
// Assign current glyph with selected shape
if (glyphsController != null) {
Glyph glyph = glyphsController.getNest()
.getSelectedGlyph();
if (glyph != null) {
String str = button.getText();
Shape shape = Shape.valueOf(str);
// Actually assign the shape
glyphsController.asyncAssignGlyphs(
Glyphs.sortedSet(glyph),
shape,
false);
}
}
}
public void setEval (Evaluation eval,
boolean barred,
boolean enabled)
{
JComponent comp;
if (sheet != null) {
comp = button;
} else {
comp = field;
}
if (eval != null) {
Evaluation.Failure failure = eval.failure;
String text = eval.shape.toString();
String tip = (failure != null) ? failure.toString()
: null;
if (sheet != null) {
button.setEnabled(enabled);
if (barred) {
button.setBackground(Colors.EVALUATION_BARRED);
} else {
button.setBackground(null);
}
button.setText(text);
button.setToolTipText(tip);
button.setIcon(eval.shape.getDecoratedSymbol());
} else {
if (barred) {
field.setBackground(Colors.EVALUATION_BARRED);
} else {
field.setBackground(null);
}
field.setText(text);
field.setToolTipText(tip);
field.setIcon(eval.shape.getDecoratedSymbol());
}
comp.setVisible(true);
if (failure == null) {
comp.setForeground(EVAL_GOOD_COLOR);
} else {
comp.setForeground(EVAL_SOSO_COLOR);
}
grade.setVisible(true);
grade.setText(String.format("%.3f", eval.grade));
} else {
grade.setVisible(false);
comp.setVisible(false);
}
}
}
//----------//
// Selector //
//----------//
private class Selector
{
//~ Instance fields ----------------------------------------------------
// A collection of EvalButton's
final List<EvalButton> buttons = new ArrayList<>();
//~ Constructors -------------------------------------------------------
public Selector ()
{
for (int i = 0; i < evalCount(); i++) {
buttons.add(new EvalButton());
}
setEvals(null, null);
}
//~ Methods ------------------------------------------------------------
//-----------//
// evalCount //
//-----------//
/**
* Report the number of displayed evaluations
*
* @return the number of eval buttons
*/
public final int evalCount ()
{
return constants.visibleButtons.getValue();
}
//----------//
// setEvals //
//----------//
/**
* Display the evaluations with some text highlighting.
* Only first evalCount evaluations are displayed.
*
* @param evals top evaluations sorted from best to worst
* @param glyph evaluated glyph, to check forbidden shapes if any
*/
public final void setEvals (Evaluation[] evals,
Glyph glyph)
{
// Special case to empty the selector
if (evals == null) {
for (EvalButton evalButton : buttons) {
evalButton.setEval(null, false, false);
}
return;
}
boolean enabled = !glyph.isVirtual();
double minGrade = constants.minGrade.getValue();
int iBound = Math.min(evalCount(), evals.length);
int i;
for (i = 0; i < iBound; i++) {
Evaluation eval = evals[i];
// Limitation on shape relevance
if (eval.grade < minGrade) {
break;
}
// Barred on non-barred button
buttons.get(i)
.setEval(
eval,
glyph.isShapeForbidden(eval.shape),
enabled);
}
// Zero the remaining buttons
for (; i < evalCount(); i++) {
buttons.get(i)
.setEval(null, false, false);
}
}
}
}