//----------------------------------------------------------------------------// // // // C h e c k P a n e l // // // //----------------------------------------------------------------------------// // <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.check; import omr.constant.Constant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import omr.ui.util.Panel; import com.jgoodies.forms.builder.PanelBuilder; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; import java.awt.Color; import java.awt.event.ActionEvent; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Scanner; import javax.swing.AbstractAction; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JTextField; import javax.swing.KeyStroke; /** * Class {@code CheckPanel} handles a panel to display the results of a * check suite. * * @param <C> the subtype of Checkable-compatible objects used in the * homogeneous collection of checks of the suite * * @author Hervé Bitteur */ public class CheckPanel<C extends Checkable> { //~ Static fields/initializers --------------------------------------------- /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(CheckPanel.class); // Colors private static final Color GREEN_COLOR = new Color(100, 150, 0); private static final Color ORANGE_COLOR = new Color(255, 150, 0); private static final Color RED_COLOR = new Color(255, 0, 0); // Sizes private static final String LINE_GAP = "1dlu"; private static final String COLUMN_GAP = "1dlu"; private static final int FIELD_WIDTH = 4; //~ Instance fields -------------------------------------------------------- // /** The related check suite (the model) */ private CheckSuite<C> suite; /** The swing component that includes all the fields */ private Panel component; /** The field for global result */ private JTextField globalField; /** Matrix of all value fields */ private JTextField[][] values; /** Matrix of all bound fields */ private JTextField[][] bounds; /** Last object checked */ private C object; //~ Constructors ----------------------------------------------------------- // //------------// // CheckPanel // //------------// /** * Create a check panel for a given suite. * * @param suite the suite whose results are to be displayed */ public CheckPanel (CheckSuite<C> suite) { // Global field (for global result) globalField = new JTextField(FIELD_WIDTH); globalField.setEditable(false); globalField.setHorizontalAlignment(JTextField.CENTER); setSuite(suite); } //~ Methods ---------------------------------------------------------------- // //--------------// // getComponent // //--------------// /** * Report the UI component. * * @return the concrete component */ public JComponent getComponent () { return component; } //----------// // passForm // //----------// /** * Pass the whole suite on the provided checkable object, and * display the results. * * @param object the object to be checked */ public void passForm (C object) { // Remember the 'current' object' this.object = object; resetValues(); if (object == null) { return; } CheckResult result = new CheckResult(); double grade = 0d; boolean failed = false; // Fill one row per check for (int index = 0; index < suite.getChecks().size(); index++) { Check<C> check = suite.getChecks().get(index); try { // Run this check check.pass(object, result, false); grade += (result.flag * suite.getWeights().get(index)); // Update proper field to display check result JTextField field; switch (result.flag) { case Check.RED: failed = true; if (check.isCovariant()) { field = values[index][0]; field.setToolTipText("Value is too low"); } else { field = values[index][2]; field.setToolTipText("Value is too high"); } field.setForeground(RED_COLOR); break; case Check.ORANGE: field = values[index][1]; field.setToolTipText("Value is acceptable"); field.setForeground(ORANGE_COLOR); break; default: case Check.GREEN: if (check.isCovariant()) { field = values[index][2]; } else { field = values[index][0]; } field.setToolTipText("Value is OK"); field.setForeground(GREEN_COLOR); break; } field.setText(textOf(result.value)); } catch (Throwable ex) { logger.warn("Failure in check " + check.getName(), ex); failed = true; } } // Global suite result if (failed) { globalField.setForeground(RED_COLOR); globalField.setToolTipText("Check has failed!"); globalField.setText("Failed"); } else { grade /= suite.getTotalWeight(); if (grade >= suite.getThreshold()) { globalField.setForeground(GREEN_COLOR); globalField.setToolTipText("Check has succeeded!"); } else { globalField.setForeground(RED_COLOR); globalField.setToolTipText("Check has failed!"); } globalField.setText(textOf(grade)); } } //----------// // setSuite // //----------// /** * Assign a (new) suite to the check pane. * * @param suite the (new) check suite to be used */ public final void setSuite (CheckSuite<C> suite) { this.suite = suite; if (suite != null) { createValueFields(); // Values createBoundFields(); // Bounds buildComponent(); // Create/update component } // Refresh the display if (component != null) { component.validate(); component.repaint(); } } //----------------// // buildComponent // //----------------// private void buildComponent () { // Either allocate a new Panel or empty the existing one if (component == null) { component = new Panel(); component.setNoInsets(); // Needed to process user input when RETURN/ENTER is pressed component.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT). put(KeyStroke.getKeyStroke("ENTER"), "ParamAction"); component.getActionMap().put("ParamAction", new ParamAction()); } else { component.removeAll(); } final int checkNb = suite.getChecks().size(); PanelBuilder b = new PanelBuilder(createLayout(checkNb), component); b.setDefaultDialogBorder(); CellConstraints c = new CellConstraints(); // Rows int ic = -1; // Check index int r = -1; // Row index for (Check<C> check : suite.getChecks()) { ic++; r += 2; // Covariance label JLabel covariantLabel; if (check.isCovariant()) { covariantLabel = new JLabel(">"); covariantLabel.setToolTipText("Higher is better"); } else { covariantLabel = new JLabel("<"); covariantLabel.setToolTipText("Lower is better"); } b.add(covariantLabel, c.xy(1, r)); // Name label with proper tooltip JLabel nameLabel = new JLabel(check.getName()); if (check.getDescription() != null) { StringBuilder sb = new StringBuilder(); sb.append("<html>"); sb.append(check.getDescription()); Constant constant = check.getLowConstant(); // Tell data unit if relevant sb.append("<br/>"); if (constant.getQuantityUnit() != null) { sb.append("Unit=").append(constant.getQuantityUnit()); } else { // Otherwise, simply tell the data type sb.append("Type=").append(constant.getShortTypeName()); } sb.append("</html>"); nameLabel.setToolTipText(sb.toString()); } b.add(nameLabel, c.xy(3, r)); // Value & bound fields b.add(values[ic][0], c.xy(5, r)); b.add(bounds[ic][0], c.xy(7, r)); b.add(values[ic][1], c.xy(9, r)); b.add(bounds[ic][1], c.xy(11, r)); b.add(values[ic][2], c.xy(13, r)); } // Last row for global result r += 2; JLabel globalLabel = new JLabel("Result"); globalLabel.setToolTipText("Global check result"); b.add(globalLabel, c.xy(5, r)); b.add(globalField, c.xy(9, r)); } //-------------------// // createBoundFields // //-------------------// private void createBoundFields () { // Allocate bound fields (2 per check) final int checkNb = suite.getChecks().size(); bounds = new JTextField[checkNb][]; for (int ic = 0; ic < checkNb; ic++) { Check<C> check = suite.getChecks().get(ic); bounds[ic] = new JTextField[2]; for (int i = 0; i <= 1; i++) { JTextField field = new JTextField(FIELD_WIDTH); field.setHorizontalAlignment(JTextField.CENTER); bounds[ic][i] = field; Constant.Double constant = (i == 0) ? check.getLowConstant() : check.getHighConstant(); field.setText(textOf(constant.getValue())); field.setToolTipText( "<html>" + constant.getName() + "<br/>" + constant.getDescription() + "</html>"); } } } //--------------// // createLayout // //--------------// private FormLayout createLayout (int checkNb) { // Build proper column specification StringBuilder sbc = new StringBuilder(); sbc.append("center:pref").append(", ").append(COLUMN_GAP).append(", "); // Covariance sbc.append(" right:40dlu").append(", ").append(COLUMN_GAP).append(", "); // Name sbc.append(" right:pref").append(", ").append(COLUMN_GAP).append(", "); sbc.append(" right:pref").append(", ").append(COLUMN_GAP).append(", "); // Low limit sbc.append(" right:pref").append(", ").append(COLUMN_GAP).append(", "); sbc.append(" right:pref").append(", ").append(COLUMN_GAP).append(", "); // High Limit sbc.append(" right:pref"); // Build proper row specification StringBuilder sbr = new StringBuilder(); for (int n = 0; n <= checkNb; n++) { if (n != 0) { sbr.append(", ").append(LINE_GAP).append(", "); } sbr.append("pref"); } logger.debug("sb cols={}", sbc); logger.debug("sb rows={}", sbr); // Create proper form layout return new FormLayout( sbc.toString(), //cols sbr.toString()); //rows } //-------------------// // createValueFields // //-------------------// private void createValueFields () { // Allocate value fields (3 per check) final int checkNb = suite.getChecks().size(); values = new JTextField[checkNb][3]; for (int n = 0; n < checkNb; n++) { for (int i = 0; i <= 2; i++) { JTextField field = new JTextField(FIELD_WIDTH); field.setEditable(false); field.setHorizontalAlignment(JTextField.CENTER); values[n][i] = field; } } } //-------------// // resetValues // //-------------// private void resetValues () { for (JTextField[] seq : values) { for (JTextField field : seq) { field.setText(""); } } } //--------// // textOf // //--------// private String textOf (double val) { return String.format(Locale.getDefault(), "%5.2f", val); } //---------// // valueOf // //---------// private double valueOf (String text) { Scanner scanner = new Scanner(text); scanner.useLocale(Locale.getDefault()); while (scanner.hasNext()) { if (scanner.hasNextDouble()) { return scanner.nextDouble(); } else { scanner.next(); } } // Kludge! return Double.NaN; } //~ Inner Classes ---------------------------------------------------------- // //-------------// // ParamAction // //-------------// private class ParamAction extends AbstractAction { //~ Methods ------------------------------------------------------------ /** * Method run whenever user presses Return/Enter in one of * the parameter fields */ @Override public void actionPerformed (ActionEvent e) { // Any & several bounds may have been modified by the user // Since the same constant can be used in several fields, we have to // take a snapshot of all constants values, before modifying any one Map<Constant.Double, Double> values = new HashMap<>(); for (Check<C> check : suite.getChecks()) { values.put( check.getLowConstant(), check.getLowConstant().getValue()); values.put( check.getHighConstant(), check.getHighConstant().getValue()); } boolean modified = false; int ic = -1; for (Check<C> check : suite.getChecks()) { ic++; // Check the bounds wrt the corresponding fields for (int i = 0; i < 2; i++) { final Constant.Double constant = (i == 0) ? check.getLowConstant() : check.getHighConstant(); // Simplistic test to detect modification final JTextField field = bounds[ic][i]; final String oldString = textOf(values.get(constant)).trim(); final String newString = field.getText().trim(); if (!oldString.equals(newString)) { StringBuilder sb = new StringBuilder(); sb.append("Check '").append(check.getName()). append("':"); if (i == 0) { sb.append(" Low"); } else { sb.append(" High"); } sb.append(" bound '").append(constant.getName()).append( "'"); final String context = sb.toString(); // Actually convert the value and update the constant try { constant.setValue(valueOf(newString)); modified = true; sb.append(" modified from ").append(oldString). append(" to ").append(newString); logger.info(sb.toString()); } catch (Exception ex) { logger.warn( "Error in {}, {}", context, ex.getLocalizedMessage()); } } } } // If at least one modification has been made, update the whole // table with both suite parameters and object results if (modified) { setSuite(suite); passForm(object); } } } }