//----------------------------------------------------------------------------// // // // S h a p e F o c u s 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.GlyphRegression; import omr.glyph.Shape; import omr.glyph.ShapeDescription; import omr.glyph.ShapeSet; import omr.glyph.facets.Glyph; import omr.math.LinearEvaluator.Printer; import omr.selection.GlyphEvent; import omr.selection.GlyphIdEvent; import omr.selection.SelectionHint; import omr.selection.UserEvent; import omr.sheet.Sheet; import omr.ui.Board; import omr.ui.field.SpinnerUtil; import static omr.ui.field.SpinnerUtil.*; 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 java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JSpinner; import javax.swing.SpinnerListModel; import javax.swing.SwingConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * Class {@code ShapeFocusBoard} handles a user iteration within a * collection of glyphs. * The collection may be built from glyphs of a given shape, * or from glyphs similar to a given glyph, etc. * * @author Hervé Bitteur */ public class ShapeFocusBoard 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( ShapeFocusBoard.class); /** Events this board is interested in */ private static final Class<?>[] eventsRead = new Class<?>[]{GlyphEvent.class}; //~ Enumerations ----------------------------------------------------------- /** Filter on which symbols should be displayed */ private static enum Filter { //~ Enumeration constant initializers ---------------------------------- /** Display all symbols */ ALL, /** Display only known symbols */ KNOWN, /** Display only unknown symbols */ UNKNOWN, /** Display only translated * symbols */ TRANSLATED, /** Display only untranslated * symbols */ UNTRANSLATED; } //~ Instance fields -------------------------------------------------------- private final Sheet sheet; /** Browser on the collection of glyphs */ private Browser browser = new Browser(); /** Button to select the shape focus */ private JButton selectButton = new JButton(); /** Filter for known / unknown symbol display */ private JComboBox<Filter> filterButton = new JComboBox<>( Filter.values()); /** Popup menu to allow shape selection */ private JPopupMenu pm = new JPopupMenu(); //~ Constructors ----------------------------------------------------------- //-----------------// // ShapeFocusBoard // //-----------------// /** * Create the instance to handle the shape focus, with pointers to * needed companions. * * @param sheet the related sheet * @param controller the related glyph controller * @param filterListener the action linked to filter button */ public ShapeFocusBoard (Sheet sheet, GlyphsController controller, ActionListener filterListener, boolean expanded) { super( Board.FOCUS, controller.getNest().getGlyphService(), eventsRead, false, expanded); this.sheet = sheet; // Tool Tips selectButton.setToolTipText("Select candidate shape"); selectButton.setHorizontalAlignment(SwingConstants.LEFT); selectButton.addActionListener( new ActionListener() { @Override public void actionPerformed (ActionEvent e) { pm.show( selectButton, selectButton.getX(), selectButton.getY()); } }); // Filter filterButton.addActionListener(filterListener); filterButton.setToolTipText( "Select displayed glyphs according to their current state"); // Popup menu for shape selection JMenuItem noFocus = new JMenuItem("No Focus"); noFocus.setToolTipText("Cancel any focus"); noFocus.addActionListener( new ActionListener() { @Override public void actionPerformed (ActionEvent e) { setCurrentShape(null); } }); pm.add(noFocus); ShapeSet.addAllShapes( pm, new ActionListener() { @Override public void actionPerformed (ActionEvent e) { JMenuItem source = (JMenuItem) e.getSource(); setCurrentShape(Shape.valueOf(source.getText())); } }); defineLayout(); // Initially, no focus setCurrentShape(null); } //~ Methods ---------------------------------------------------------------- //-------------// // isDisplayed // //-------------// /** * Report whether the glyph at hand is to be displayed, according to * the current filter * * @param glyph the glyph at hand, perhaps null * @return true if to be displayed */ public boolean isDisplayed (Glyph glyph) { switch ((Filter) filterButton.getSelectedItem()) { case KNOWN: return (glyph != null) && glyph.isKnown(); case UNKNOWN: return (glyph == null) || !glyph.isKnown(); case TRANSLATED: return (glyph != null) && glyph.isKnown() && glyph.isTranslated(); case UNTRANSLATED: return (glyph != null) && glyph.isKnown() && !glyph.isTranslated(); default: case ALL: return true; } } //---------// // onEvent // //---------// /** * Notification about selection objects. * We used to use it on a just modified glyph, to set the new shape focus * But this conflicts with the ability to browse a collection of similar * glyphs and assign them on the fly * * @param event the notified event */ @Override public void onEvent (UserEvent event) { // Empty } //-----------------// // setCurrentShape // //-----------------// /** * Define the new current shape. * * @param currentShape the shape to be considered as current */ public void setCurrentShape (Shape currentShape) { browser.resetIds(); if (currentShape != null) { // Update the shape button selectButton.setText(currentShape.toString()); selectButton.setIcon(currentShape.getDecoratedSymbol()); // Count the number of glyphs assigned to current shape for (Glyph glyph : sheet.getActiveGlyphs()) { if (glyph.getShape() == currentShape) { browser.addId(glyph.getId()); } } setSelected(true); setVisible(true); } else { // Void the shape button selectButton.setText("- No Focus -"); selectButton.setIcon(null); } browser.refresh(); } //-----------------// // setSimilarGlyph // //-----------------// /** * Define the glyphs collection as all glyphs whose physical * appearance is "similar" to the appearance of the provided glyph * example. * * @param example the provided example */ public void setSimilarGlyph (Glyph example) { browser.resetIds(); if (example != null) { GlyphRegression evaluator = GlyphRegression.getInstance(); double[] pattern = ShapeDescription.features(example); List<DistIdPair> pairs = new ArrayList<>(); // Retrieve the glyphs similar to the example for (Glyph glyph : sheet.getActiveGlyphs()) { double dist = evaluator.measureDistance(glyph, pattern); pairs.add(new DistIdPair(dist, glyph.getId())); } Collections.sort(pairs, DistIdPair.distComparator); for (DistIdPair pair : pairs) { browser.addId(pair.id); } // To get a detailed table of the distances (debugging) if (constants.printDistances.getValue()) { Printer printer = evaluator.getEngine().new Printer(11); String indent = " "; System.out.println(indent + printer.getDefaults()); System.out.println(indent + printer.getNames()); System.out.println(indent + printer.getDashes()); for (DistIdPair pair : pairs) { Glyph glyph = sheet.getVerticalsController() .getGlyphById(pair.id); double[] gPat = ShapeDescription.features(glyph); Shape shape = glyph.getShape(); System.out.printf( "%18s", (shape != null) ? shape.toString() : ""); System.out.println(printer.getDeltas(gPat, pattern)); System.out.printf("g#%04d d:%9f", pair.id, pair.dist); System.out.println( printer.getWeightedDeltas(gPat, pattern)); } } // Update the shape button selectButton.setText("Glyphs similar to #" + example.getId()); selectButton.setIcon(null); setSelected(true); setVisible(true); } else { // Void the shape button selectButton.setText("- No Focus -"); selectButton.setIcon(null); } browser.refresh(); } //--------------// // defineLayout // //--------------// private void defineLayout () { final String fieldInterline = Panel.getFieldInterline(); String colSpec = Panel.makeColumns(3); FormLayout layout = new FormLayout( colSpec, "pref," + fieldInterline + "," + "pref"); PanelBuilder builder = new PanelBuilder(layout, getBody()); builder.setDefaultDialogBorder(); CellConstraints cst = new CellConstraints(); int r = 1; // -------------------------------- builder.add(browser.count, cst.xy(1, r)); builder.add(browser.spinner, cst.xy(3, r)); builder.add(selectButton, cst.xywh(7, r, 5, 3)); r += 2; // -------------------------------- builder.add(filterButton, cst.xyw(1, r, 3)); } //~ Inner Classes ---------------------------------------------------------- //------------// // DistIdPair // //------------// /** * Needed to sort glyphs id according to their distance */ private static class DistIdPair { //~ Static fields/initializers ----------------------------------------- private static final Comparator<DistIdPair> distComparator = new Comparator<DistIdPair>() { @Override public int compare (DistIdPair o1, DistIdPair o2) { return Double.compare(o1.dist, o2.dist); } }; //~ Instance fields ---------------------------------------------------- final double dist; final int id; //~ Constructors ------------------------------------------------------- public DistIdPair (double dist, int id) { this.dist = dist; this.id = id; } //~ Methods ------------------------------------------------------------ @Override public String toString () { return "dist:" + dist + " glyph#" + id; } } //---------// // Browser // //---------// private class Browser implements ChangeListener { //~ Instance fields ---------------------------------------------------- // Spinner on these glyphs ArrayList<Integer> ids = new ArrayList<>(); // Number of glyphs JLabel count = new JLabel("", SwingConstants.RIGHT); JSpinner spinner = new JSpinner(new SpinnerListModel()); //~ Constructors ------------------------------------------------------- //---------// // Browser // //---------// public Browser () { resetIds(); spinner.addChangeListener(this); SpinnerUtil.setList(spinner, ids); refresh(); } //~ Methods ------------------------------------------------------------ //-------// // addId // //-------// public void addId (int id) { ids.add(id); } //---------// // refresh // //---------// public void refresh () { if (ids.size() > 1) { // To skip first NO_VALUE item count.setText(0 + "/" + (ids.size() - 1)); spinner.setEnabled(true); } else { count.setText(""); spinner.setEnabled(false); } spinner.setValue(NO_VALUE); } //----------// // resetIds // //----------// public void resetIds () { ids.clear(); ids.add(NO_VALUE); } //--------------// // stateChanged // //--------------// @Override public void stateChanged (ChangeEvent e) { int id = (Integer) spinner.getValue(); int index = ids.indexOf(id); count.setText(index + "/" + (ids.size() - 1)); if (id != NO_VALUE) { getSelectionService() .publish( new GlyphIdEvent(this, SelectionHint.GLYPH_INIT, null, id)); } } } //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { //~ Instance fields ---------------------------------------------------- Constant.Boolean printDistances = new Constant.Boolean( false, "Should we print out distance details when looking for similar glyphs?"); } }