//----------------------------------------------------------------------------// // // // S h a p e S e t // // // //----------------------------------------------------------------------------// // <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; import omr.constant.Constant; import static omr.glyph.Shape.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.Color; import java.awt.event.ActionListener; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuItem; /** * Class {@code ShapeSet} defines a set of related shapes, * for example the "Rests" set gathers all rest shapes from * MULTI_REST down to ONE_128TH_REST. * * <p>It handles additional properties over a simple EnumSet, especially * assigned colors and its automatic insertion in shape menu hierarchy. * So don't remove any of the ShapeSet's, unless you know what you are doing. */ public class ShapeSet { //~ Static fields/initializers --------------------------------------------- /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(ShapeSet.class); /** Specific single symbol for part of time signature (such as 4) */ public static final List<Shape> PartialTimes = Arrays.asList( TIME_ZERO, TIME_ONE, TIME_TWO, TIME_THREE, TIME_FOUR, TIME_FIVE, TIME_SIX, TIME_SEVEN, TIME_EIGHT, TIME_NINE, TIME_TWELVE, TIME_SIXTEEN); /** Specific multi-symbol for full time signature (such as 4/4 or C) */ public static final EnumSet<Shape> FullTimes = EnumSet.of( TIME_FOUR_FOUR, TIME_TWO_TWO, TIME_TWO_FOUR, TIME_THREE_FOUR, TIME_SIX_EIGHT, COMMON_TIME, CUT_TIME, CUSTOM_TIME); /** All sorts of F clefs */ public static final EnumSet<Shape> BassClefs = EnumSet.of( F_CLEF, F_CLEF_SMALL, F_CLEF_8VA, F_CLEF_8VB); /** All sorts of G clefs */ public static final EnumSet<Shape> TrebleClefs = EnumSet.of( G_CLEF, G_CLEF_SMALL, G_CLEF_8VA, G_CLEF_8VB); /** All sorts of flag sets */ public static final EnumSet<Shape> FlagSets = EnumSet.of( FLAG_1_set, FLAG_2_set, FLAG_3_set, FLAG_4_set, FLAG_5_set); /** All SHARP-based keys */ public static final EnumSet<Shape> SharpKeys = EnumSet.of( KEY_SHARP_1, KEY_SHARP_2, KEY_SHARP_3, KEY_SHARP_4, KEY_SHARP_5, KEY_SHARP_6, KEY_SHARP_7); /** All FLAT-based keys */ public static final EnumSet<Shape> FlatKeys = EnumSet.of( KEY_FLAT_1, KEY_FLAT_2, KEY_FLAT_3, KEY_FLAT_4, KEY_FLAT_5, KEY_FLAT_6, KEY_FLAT_7); /** All black note heads */ public static final EnumSet<Shape> BlackNoteHeads = EnumSet.of( NOTEHEAD_BLACK, NOTEHEAD_BLACK_2, NOTEHEAD_BLACK_3); /** All void note heads */ public static final EnumSet<Shape> VoidNoteHeads = EnumSet.of( NOTEHEAD_VOID, NOTEHEAD_VOID_2, NOTEHEAD_VOID_3); /** * Predefined instances of ShapeSet. * Double-check before removing any one of them. * Nota: Do not use EnumSet.range() since this could lead to subtle errors * should Shape enum order be modified. Prefer the use of shapesOf() * which lists precisely all set members. */ public static final ShapeSet Accidentals = new ShapeSet( SHARP, new Color(0x9933ff), shapesOf(FLAT, NATURAL, SHARP, DOUBLE_SHARP, DOUBLE_FLAT)); public static final ShapeSet Articulations = new ShapeSet( ARPEGGIATO, new Color(0xff6699), shapesOf( ACCENT, TENUTO, STACCATO, STACCATISSIMO, STRONG_ACCENT, ARPEGGIATO)); public static final ShapeSet Attributes = new ShapeSet( PEDAL_MARK, new Color(0x000000), shapesOf( OTTAVA_ALTA, OTTAVA_BASSA, PEDAL_MARK, PEDAL_UP_MARK, TUPLET_THREE, TUPLET_SIX)); public static final ShapeSet Barlines = new ShapeSet( LEFT_REPEAT_SIGN, new Color(0x0000ff), shapesOf( PART_DEFINING_BARLINE, THIN_BARLINE, THICK_BARLINE, DOUBLE_BARLINE, FINAL_BARLINE, REVERSE_FINAL_BARLINE, LEFT_REPEAT_SIGN, RIGHT_REPEAT_SIGN, BACK_TO_BACK_REPEAT_SIGN)); public static final ShapeSet Beams = new ShapeSet( BEAM, new Color(0x33ffff), shapesOf(BEAM, BEAM_2, BEAM_3, BEAM_HOOK)); public static final ShapeSet Clefs = new ShapeSet( G_CLEF, new Color(0xcc00cc), shapesOf(TrebleClefs, BassClefs, shapesOf(C_CLEF, PERCUSSION_CLEF))); public static final ShapeSet Dynamics = new ShapeSet( DYNAMICS_F, new Color(0x009999), shapesOf( DYNAMICS_CHAR_M, DYNAMICS_CHAR_R, DYNAMICS_CHAR_S, DYNAMICS_CHAR_Z, DYNAMICS_F, DYNAMICS_FF, DYNAMICS_FFF, DYNAMICS_FP, DYNAMICS_FZ, DYNAMICS_MF, DYNAMICS_MP, DYNAMICS_P, DYNAMICS_PP, DYNAMICS_PPP, DYNAMICS_RF, DYNAMICS_RFZ, DYNAMICS_SF, DYNAMICS_SFFZ, DYNAMICS_SFP, DYNAMICS_SFPP, DYNAMICS_SFZ, CRESCENDO, DECRESCENDO)); public static final ShapeSet Flags = new ShapeSet( FLAG_1, new Color(0x99cc00), shapesOf( FLAG_1, FLAG_1_UP, FLAG_2, FLAG_2_UP, FLAG_3, FLAG_3_UP, FLAG_4, FLAG_4_UP, FLAG_5, FLAG_5_UP)); public static final ShapeSet Keys = new ShapeSet( KEY_SHARP_3, new Color(0x00ffff), shapesOf(FlatKeys, SharpKeys)); public static final ShapeSet NoteHeads = new ShapeSet( NOTEHEAD_BLACK, new Color(0xff9966), shapesOf(BlackNoteHeads, VoidNoteHeads)); public static final ShapeSet Markers = new ShapeSet( CODA, new Color(0x888888), shapesOf( DAL_SEGNO, DA_CAPO, SEGNO, CODA, BREATH_MARK, CAESURA, BRACE, BRACKET)); public static final ShapeSet Notes = new ShapeSet( BREVE, new Color(0xff66cc), shapesOf(BREVE, WHOLE_NOTE, WHOLE_NOTE_2, WHOLE_NOTE_3)); public static final ShapeSet Ornaments = new ShapeSet( MORDENT, new Color(0xcc3300), shapesOf( GRACE_NOTE_SLASH, GRACE_NOTE, TR, TURN, INVERTED_TURN, TURN_UP, TURN_SLASH, MORDENT, INVERTED_MORDENT)); public static final ShapeSet Rests = new ShapeSet( QUARTER_REST, new Color(0x99ff66), shapesOf( LONG_REST, BREVE_REST, WHOLE_REST, HALF_REST, QUARTER_REST, EIGHTH_REST, ONE_16TH_REST, ONE_32ND_REST, ONE_64TH_REST, ONE_128TH_REST)); public static final ShapeSet Times = new ShapeSet( TIME_FOUR_FOUR, new Color(0xcc3300), shapesOf(PartialTimes, FullTimes)); public static final ShapeSet Physicals = new ShapeSet( LEDGER, new Color(0x9999ff), shapesOf( TEXT, CHARACTER, CLUTTER, SLUR, LEDGER, STEM, ENDING, DOT_set, REPEAT_DOT, AUGMENTATION_DOT)); // ========================================================================= // Below are EnumSet instances, used programmatically. // They do not lead to shape submenus as the ShapeSet instances do. // ========================================================================= /** All physical shapes. Here the use of EnumSet.range is OK */ public static final EnumSet<Shape> allPhysicalShapes = EnumSet.range( Shape.values()[0], LAST_PHYSICAL_SHAPE); /** Symbols that can be attached to a stem */ public static final EnumSet<Shape> StemSymbols = EnumSet. copyOf( shapesOf(NoteHeads.getShapes(), Flags.getShapes(), Beams.getShapes())); /** Pedals */ public static final EnumSet<Shape> Pedals = EnumSet.of( PEDAL_MARK, PEDAL_UP_MARK); /** Tuplets */ public static final EnumSet<Shape> Tuplets = EnumSet.of( TUPLET_THREE, TUPLET_SIX); /** All variants of dot */ public static final EnumSet<Shape> Dots = EnumSet.of( DOT_set, AUGMENTATION_DOT, STACCATO, REPEAT_DOT); /** Clefs ottava (alta or bassa) */ public static final EnumSet<Shape> OttavaClefs = EnumSet.of( G_CLEF_8VA, G_CLEF_8VB, F_CLEF_8VA, F_CLEF_8VB); static { // Make sure all the shape colors are defined ShapeSet.defineAllShapeColors(); // Debug ///dumpShapeColors(); } //~ Instance fields -------------------------------------------------------- // /** Name of the set */ private String name; /** Underlying shapes */ private final EnumSet<Shape> shapes; /** Specific sequence of shapes, if any */ private final List<Shape> sortedShapes; /** The representative shape for this set */ private final Shape rep; /** Assigned color */ private Color color; /** Related color constant */ private Constant.Color constantColor; //~ Constructors ----------------------------------------------------------- //----------// // ShapeSet // //----------// /** * Creates a new ShapeSet object from a collection of shapes. * * @param rep the representative shape * @param color the default color assigned * @param shapes the provided collection of shapes */ public ShapeSet (Shape rep, Color color, Collection<Shape> shapes) { // The representative shape this.rep = rep; // The default color this.color = color != null ? color : Color.BLACK; // The set of shapes this.shapes = EnumSet.noneOf(Shape.class); this.shapes.addAll(shapes); // Keep a specific order? if (shapes instanceof List) { this.sortedShapes = new ArrayList<>(shapes); } else { this.sortedShapes = null; } } //~ Methods ---------------------------------------------------------------- // //-----------------------// // getPhysicalShapeNames // //-----------------------// /** * Report the names of all the physical shapes. * * @return the array of names for shapes up to LAST_PHYSICAL_SHAPE */ public static String[] getPhysicalShapeNames () { int shapeCount = 1 + LAST_PHYSICAL_SHAPE.ordinal(); String[] names = new String[shapeCount]; for (Shape shape : allPhysicalShapes) { names[shape.ordinal()] = shape.name(); } return names; } //-----------------// // addAllShapeSets // //-----------------// /** * Populate the given menu with all ShapeSet instances defined * in this class. * * @param top the JComponent to populate (typically a JMenu or a * JPopupMenu) * @param listener the listener for notification of user selection */ public static void addAllShapeSets (JComponent top, ActionListener listener) { // All ranges of glyph shapes for (Field field : ShapeSet.class.getDeclaredFields()) { if (field.getType() == ShapeSet.class) { JMenuItem menuItem = new JMenuItem(field.getName()); ShapeSet set = valueOf(field.getName()); addColoredItem(top, menuItem, set.getColor()); menuItem.addActionListener(listener); } } } //--------------// // addAllShapes // //--------------// /** * Populate the given menu with a hierarchy of all shapes, * organized by defined ShapeSets. * * @param top the JComponent to populate (typically a JMenu or a * JPopupMenu) * @param listener the listener for notification of user selection */ public static void addAllShapes (JComponent top, ActionListener listener) { // All ranges of glyph shapes for (Field field : ShapeSet.class.getDeclaredFields()) { if (field.getType() == ShapeSet.class) { ShapeSet set = ShapeSet.valueOf(field.getName()); JMenu menu = new JMenu(field.getName()); if (set.rep != null) { menu.setIcon(set.rep.getDecoratedSymbol()); } addColoredItem(top, menu, Color.black); // Add menu items for this range addSetShapes(set, menu, listener); } } } //--------------// // addSetShapes // //--------------// /** * Populate the given menu with a list of all shapes that belong * to the given ShapeSet. * * @param set the set for which shape menu items must be buit * @param top the JComponent to populate (typically a JMenu or a * JPopupMenu) * @param listener the listener for notification of user selection */ public static void addSetShapes (ShapeSet set, JComponent top, ActionListener listener) { // All shapes in the given range for (Shape shape : set.getSortedShapes()) { JMenuItem menuItem = new JMenuItem( shape.toString(), shape.getDecoratedSymbol()); addColoredItem(top, menuItem, shape.getColor()); menuItem.setToolTipText(shape.getDescription()); menuItem.addActionListener(listener); } } //----------// // contains // //----------// /** * Convenient method to check if encapsulated shapes set does * contain the provided object. * * @param shape the Shape object to check for inclusion * @return true if contained, false otherwise */ public boolean contains (Shape shape) { return shapes.contains(shape); } //----------// // getColor // //----------// /** * Report the color currently assigned to the range, if any. * * @return the related color, or null */ public Color getColor () { return color; } //---------// // getName // //---------// /** * Report the name of the set. * * @return the set name */ public String getName () { return name; } //--------// // getRep // //--------// /** * Report the representative shape of the set, if any. * * @return the rep shape, or null */ public Shape getRep () { return rep; } //-------------// // getShapeSet // //-------------// public static ShapeSet getShapeSet (String name) { return Sets.map.get(name); } //--------------// // getShapeSets // //--------------// public static List<ShapeSet> getShapeSets () { return Sets.setList; } //----------// // shapesOf // //----------// /** * Convenient way to build a collection of shapes. * * @param col a collection of shapes * @return a single collection */ public static Collection<Shape> shapesOf (Collection<Shape> col) { Collection<Shape> shapes = (col instanceof List) ? new ArrayList<Shape>() : EnumSet.noneOf(Shape.class); shapes.addAll(col); return shapes; } //----------// // shapesOf // //----------// /** * Convenient way to build a collection of shapes. * * @param col1 a first collection of shapes * @param col2 a second collection of shapes * @return a single collection */ public static Collection<Shape> shapesOf (Collection<Shape> col1, Collection<Shape> col2) { Collection<Shape> shapes = (col1 instanceof List) ? new ArrayList<Shape>() : EnumSet.noneOf(Shape.class); shapes.addAll(col1); shapes.addAll(col2); return shapes; } //----------// // shapesOf // //----------// /** * Convenient way to build a collection of shapes. * * @param col1 a first collection of shapes * @param col2 a second collection of shapes * @param col3 a third collection of shapes * @return a single collection */ public static Collection<Shape> shapesOf (Collection<Shape> col1, Collection<Shape> col2, Collection<Shape> col3) { Collection<Shape> shapes = (col1 instanceof List) ? new ArrayList<Shape>() : EnumSet.noneOf(Shape.class); shapes.addAll(col1); shapes.addAll(col2); shapes.addAll(col3); return shapes; } //----------// // shapesOf // //----------// /** * Convenient way to build a collection of shapes. * * @param shapes an array of shapes * @return a single collection */ public static Collection<Shape> shapesOf (Shape... shapes) { return Arrays.asList(shapes); } //---------// // valueOf // //---------// /** * Retrieve a set knowing its name (just like an enumeration). * * @param str the provided set name * @return the range found, or null otherwise */ public static ShapeSet valueOf (String str) { return Sets.map.get(str); } //-----------// // getShapes // //-----------// /** * Exports the set of shapes. * * @return the proper enum set */ public EnumSet<Shape> getShapes () { return shapes; } //-----------------// // getSortedShapes // //-----------------// /** * Exports the sorted collection of shapes. * * @return the proper enum set */ public List<Shape> getSortedShapes () { if (sortedShapes != null) { return sortedShapes; } else { return new ArrayList<>(shapes); } } //----------// // setColor // //----------// /** * Assign a display color to the shape set. * * @param color the display color */ private void setColor (Color color) { this.color = color; } //------------------// // setConstantColor // //------------------// /** * Define a specific color for the set. * * @param color the specified color */ public void setConstantColor (Color color) { constantColor.setValue(color); setColor(color); } //----------------------// // defineAllShapeColors // //----------------------// /** * (package private access meant from Shape class) * Assign a color to every shape, using the color of the containing * set when no specific color is defined for a shape. */ static void defineAllShapeColors () { EnumSet<Shape> colored = EnumSet.noneOf(Shape.class); // Define shape colors, using their containing range as default for (Field field : ShapeSet.class.getDeclaredFields()) { if (field.getType() == ShapeSet.class) { try { ShapeSet set = (ShapeSet) field.get(null); set.setName(field.getName()); // Create shape color for all contained shapes for (Shape shape : set.shapes) { shape.createShapeColor(set.getColor()); colored.add(shape); } } catch (IllegalAccessException ex) { ex.printStackTrace(); } } } /** Sets of similar shapes */ HW_REST_set.createShapeColor(Rests.getColor()); colored.add(HW_REST_set); // Directly assign colors for shapes in no range EnumSet<Shape> leftOver = EnumSet.allOf(Shape.class); leftOver.removeAll(colored); for (Shape shape : leftOver) { shape.createShapeColor(Color.BLACK); } } //----------------// // addColoredItem // //----------------// private static void addColoredItem (JComponent top, JMenuItem item, Color color) { if (color != null) { item.setForeground(color); } else { item.setForeground(Color.black); } top.add(item); } //---------// // setName // //---------// private void setName (String name) { this.name = name; constantColor = new Constant.Color( getClass().getName(), name + ".color", Constant.Color.encodeColor(color), "Color code for set " + name); // Check for a user-modified value if (!constantColor.isSourceValue()) { setColor(constantColor.getValue()); } } //~ Inner Classes ---------------------------------------------------------- //------// // Sets // //------// /** Build the set map in a lazy way */ private static class Sets { //~ Static fields/initializers ----------------------------------------- static final Map<String, ShapeSet> map = new HashMap<>(); static final List<ShapeSet> setList = new ArrayList<>(); static { for (Field field : ShapeSet.class.getDeclaredFields()) { if (field.getType() == ShapeSet.class) { try { ShapeSet set = (ShapeSet) field.get(null); map.put(field.getName(), set); setList.add(set); } catch (IllegalAccessException ex) { ex.printStackTrace(); } } } } private Sets () { } } }