//----------------------------------------------------------------------------// // // // G l y p h 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.ConstantSet; import omr.glyph.Nest; import omr.glyph.Shape; import omr.glyph.facets.Glyph; import omr.selection.GlyphEvent; import omr.selection.GlyphIdEvent; import omr.selection.GlyphSetEvent; import omr.selection.MouseMovement; import omr.selection.SelectionHint; import omr.selection.UserEvent; import omr.ui.Board; import omr.ui.PixelCount; import omr.ui.field.LTextField; import omr.ui.field.SpinnerUtil; import static omr.ui.field.SpinnerUtil.*; import omr.ui.util.Panel; import omr.util.BasicTask; import omr.util.Predicate; import com.jgoodies.forms.builder.PanelBuilder; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; import org.jdesktop.application.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JSpinner; import javax.swing.SwingConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * Class {@code GlyphBoard} defines a UI board dedicated to the display * of {@link Glyph} information. * * <p>The universal <b>globalSpinner</b> addresses <i>all</i> glyphs * currently defined in the nest (note that glyphs can be dynamically created or * destroyed). * * <p>The spinner can be used to select a glyph by directly entering the * glyph id value into the spinner field * * @author Hervé Bitteur */ public class GlyphBoard extends Board implements ChangeListener // For all spinners { //~ Static fields/initializers --------------------------------------------- /** Specific application parameters */ private static final Constants constants = new Constants(); /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger( GlyphBoard.class); /** Events this board is interested in */ private static final Class<?>[] eventClasses = new Class<?>[]{ GlyphEvent.class, GlyphSetEvent.class }; /** Predicate for known glyphs */ protected static final Predicate<Glyph> knownPredicate = new Predicate<Glyph>() { @Override public boolean check (Glyph glyph) { return (glyph != null) && glyph.isKnown(); } }; //~ Instance fields -------------------------------------------------------- /** The related glyph model */ protected final GlyphsController controller; /** An active label */ protected final JLabel active = new JLabel("", SwingConstants.CENTER); /** Input: Dump */ protected final JButton dump; /** Counter of glyph selection */ protected final JLabel count = new JLabel(""); /** Input : Deassign action */ protected Action deassignAction; /** Output : glyph shape icon */ protected final JLabel shapeIcon = new JLabel(); /** Input / Output : spinner of all glyphs */ protected JSpinner globalSpinner; /** Input / Output : spinner of known glyphs */ protected JSpinner knownSpinner; /** Output : shape of the glyph */ protected final LTextField shapeField = new LTextField( "", "Assigned shape for this glyph"); /** The JGoodies/Form constraints to be used by all subclasses */ protected final CellConstraints cst = new CellConstraints(); /** The JGoodies/Form layout to be used by all subclasses */ protected final FormLayout layout = Panel.makeFormLayout(6, 3); /** The JGoodies/Form builder to be used by all subclasses */ protected final PanelBuilder builder; /** * We have to avoid endless loop, due to related modifications : When a * GLYPH selection is notified, the id spinner is changed, and When an id * spinner is changed, the GLYPH selection is notified */ protected boolean selfUpdating = false; //~ Constructors ----------------------------------------------------------- //------------// // GlyphBoard // //------------// /** * Basic constructor, to set common characteristics. * * @param controller the related glyphs controller, if any * @param useSpinners true for use of spinners * @param expanded true if board must be initially expanded */ public GlyphBoard (GlyphsController controller, boolean useSpinners, boolean expanded) { super( Board.GLYPH, controller.getNest().getGlyphService(), eventClasses, true, // Dump expanded); this.controller = controller; // Dump dump = getDumpButton(); dump.setToolTipText("Dump this glyph"); dump.addActionListener( new ActionListener() { @Override public void actionPerformed (ActionEvent e) { // Retrieve current glyph selection GlyphEvent glyphEvent = (GlyphEvent) getSelectionService() .getLastEvent( GlyphEvent.class); Glyph glyph = glyphEvent.getData(); if (glyph != null) { logger.info(glyph.dumpOf()); } } }); // Until a glyph selection is made dump.setEnabled(false); getDeassignAction() .setEnabled(false); // Force a constant height for the shapeIcon field, despite the // variation in size of the icon Dimension dim = new Dimension( constants.shapeIconWidth.getValue(), constants.shapeIconHeight.getValue()); shapeIcon.setPreferredSize(dim); shapeIcon.setMaximumSize(dim); shapeIcon.setMinimumSize(dim); // Precise layout // layout.setColumnGroups( // new int[][] { // { 1, 5, 9 }, // { 3, 7, 11 } // }); builder = new PanelBuilder(layout, getBody()); builder.setDefaultDialogBorder(); defineLayout(); if (useSpinners) { // Model for globalSpinner globalSpinner = makeGlyphSpinner(controller.getNest(), null); globalSpinner.setName("globalSpinner"); globalSpinner.setToolTipText("General spinner for any glyph id"); // Layout int r = 1; // -------------------------------- if (globalSpinner != null) { builder.addLabel("Id", cst.xy(1, r)); builder.add(globalSpinner, cst.xy(3, r)); } } } //~ Methods ---------------------------------------------------------------- //-------------------// // getDeassignAction // //-------------------// /** * Give access to the Deassign Action, to modify its properties * * @return the deassign action */ public Action getDeassignAction () { if (deassignAction == null) { deassignAction = new DeassignAction(); } return deassignAction; } //---------// // onEvent // //---------// /** * Call-back triggered when Glyph Selection has been modified * * @param event of current glyph or glyph set */ @Override public void onEvent (UserEvent event) { logger.debug("GlyphBoard event:{}", event); try { // Ignore RELEASING if (event.movement == MouseMovement.RELEASING) { return; } logger.debug( "GlyphBoard selfUpdating={} : {}", selfUpdating, event); if (event instanceof GlyphEvent) { // Display Glyph parameters (while preventing circular updates) selfUpdating = true; handleEvent((GlyphEvent) event); selfUpdating = false; } else if (event instanceof GlyphSetEvent) { // Display count of glyphs in the glyph set handleEvent((GlyphSetEvent) event); } } catch (Exception ex) { logger.warn(getClass().getName() + " onEvent error", ex); } } //--------------// // stateChanged // //--------------// /** * CallBack triggered by a change in one of the spinners. * * @param e the change event, this allows to retrieve the originating * spinner */ @Override public void stateChanged (ChangeEvent e) { JSpinner spinner = (JSpinner) e.getSource(); // Nota: this method is automatically called whenever the spinner value // is changed, including when a GLYPH selection notification is // received leading to such selfUpdating. So the check. if (!selfUpdating) { // Notify the new glyph id getSelectionService() .publish( new GlyphIdEvent( this, SelectionHint.GLYPH_INIT, null, (Integer) spinner.getValue())); } } //--------------// // defineLayout // //--------------// /** * Define the layout for common fields of all GlyphBoard classes */ protected void defineLayout () { int r = 1; // -------------------------------- // Shape Icon (start, spans several rows) + count + active + Deassign button builder.add(shapeIcon, cst.xywh(1, r, 1, 5)); builder.add(count, cst.xy(5, r)); builder.add(active, cst.xy(7, r)); JButton deassignButton = new JButton(getDeassignAction()); deassignButton.setHorizontalTextPosition(SwingConstants.LEFT); deassignButton.setHorizontalAlignment(SwingConstants.RIGHT); builder.add(deassignButton, cst.xyw(9, r, 3)); r += 2; // -------------------------------- // Shape name builder.add(shapeField.getField(), cst.xyw(7, r, 5)); } //------------------// // makeGlyphSpinner // //------------------// /** * Convenient method to allocate a glyph-based spinner * * @param nest the underlying glyph nest * @param predicate a related glyph predicate, if any * @return the spinner built */ protected JSpinner makeGlyphSpinner (Nest nest, Predicate<Glyph> predicate) { JSpinner spinner = new JSpinner(); spinner.setModel(new SpinnerGlyphModel(nest, predicate)); spinner.addChangeListener(this); SpinnerUtil.setRightAlignment(spinner); SpinnerUtil.setEditable(spinner, true); return spinner; } //-------------// // handleEvent // //-------------// /** * Interest in Glyph * * @param GlyphEvent */ private void handleEvent (GlyphEvent glyphEvent) { // Display Glyph parameters Glyph glyph = glyphEvent.getData(); // Active ? if (glyph != null) { if (glyph.isActive()) { if (glyph.isVirtual()) { active.setText("Virtual"); } else { active.setText("Active"); } } else { active.setText("Non Active"); } } else { active.setText(""); } // Dump button and deassign button dump.setEnabled(glyph != null); getDeassignAction() .setEnabled((glyph != null) && glyph.isKnown()); // Shape text and icon Shape shape = (glyph != null) ? glyph.getShape() : null; if (shape != null) { if ((shape == Shape.GLYPH_PART) && (glyph.getPartOf() != null)) { shapeField.setText(shape + " of #" + glyph.getPartOf().getId()); } else { shapeField.setText(shape.toString()); } shapeIcon.setIcon(shape.getDecoratedSymbol()); } else { shapeField.setText(""); shapeIcon.setIcon(null); } // Global Spinner if (globalSpinner != null) { if (glyph != null) { globalSpinner.setValue(glyph.getId()); } else { globalSpinner.setValue(NO_VALUE); } } // Known Spinner if (knownSpinner != null) { if (glyph != null) { knownSpinner.setValue( knownPredicate.check(glyph) ? glyph.getId() : NO_VALUE); } else { knownSpinner.setValue(NO_VALUE); } } } //-------------// // handleEvent // //-------------// /** * Interest in GlyphSet * * @param GlyphSetEvent */ private void handleEvent (GlyphSetEvent glyphSetEvent) { // Display count of glyphs in the glyph set Set<Glyph> glyphs = glyphSetEvent.getData(); if ((glyphs != null) && !glyphs.isEmpty()) { count.setText(Integer.toString(glyphs.size())); } else { count.setText(""); } } //~ Inner Classes ---------------------------------------------------------- //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { //~ Instance fields ---------------------------------------------------- /** Exact pixel height for the shape icon field */ PixelCount shapeIconHeight = new PixelCount( 70, "Exact pixel height for the shape icon field"); /** Exact pixel width for the shape icon field */ PixelCount shapeIconWidth = new PixelCount( 50, "Exact pixel width for the shape icon field"); } //----------------// // DeassignAction // //----------------// private class DeassignAction extends AbstractAction { //~ Constructors ------------------------------------------------------- public DeassignAction () { super("Deassign"); this.putValue(Action.SHORT_DESCRIPTION, "Deassign shape"); } //~ Methods ------------------------------------------------------------ @SuppressWarnings("unchecked") @Override public void actionPerformed (ActionEvent e) { List<Class<?>> classes = Arrays.asList(eventClasses); if ((controller != null) && !classes.isEmpty()) { // Do we have selections for glyph set, or just for glyph? if (classes.contains(GlyphEvent.class)) { final Glyph glyph = (Glyph) getSelectionService() .getSelection( GlyphEvent.class); if (classes.contains(GlyphSetEvent.class)) { final Set<Glyph> glyphs = (Set<Glyph>) getSelectionService() .getSelection( GlyphSetEvent.class); boolean noVirtuals = true; for (Glyph g : glyphs) { if (g.isVirtual()) { noVirtuals = false; break; } } if (noVirtuals) { new BasicTask() { @Override protected Void doInBackground () throws Exception { // Following actions must be done in sequence Task task = controller.asyncDeassignGlyphs( glyphs); if (task != null) { task.get(); // Update focus on current glyph, // even if reused in a compound Glyph newGlyph = glyph.getFirstSection() .getGlyph(); getSelectionService() .publish( new GlyphEvent( this, SelectionHint.GLYPH_INIT, null, newGlyph)); } return null; } }.execute(); } else { new BasicTask() { @Override protected Void doInBackground () throws Exception { // Following actions must be done in sequence Task task = controller.asyncDeleteVirtualGlyphs( glyphs); if (task != null) { task.get(); // Null publication getSelectionService() .publish( new GlyphEvent( this, SelectionHint.GLYPH_INIT, null, null)); } return null; } }.execute(); } } else { // We have selection for glyph only if (glyph.isVirtual()) { controller.asyncDeleteVirtualGlyphs( Collections.singleton(glyph)); } else { controller.asyncDeassignGlyphs( Collections.singleton(glyph)); } } } } } } }