<commit_before>//------------------------------------------------------------------------------------------------//
//                                                                                                //
//                                      S h a p e B o a r d                                       //
//                                                                                                //
//------------------------------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr">
//
//  Copyright © Audiveris 2019. All rights reserved.
//
//  This program is free software: you can redistribute it and/or modify it under the terms of the
//  GNU Affero General Public License as published by the Free Software Foundation, either version
//  3 of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
//  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//  See the GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License along with this
//  program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------------------------//
// </editor-fold>
package org.audiveris.omr.sig.ui;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

import org.audiveris.omr.OMR;
import org.audiveris.omr.constant.Constant;
import org.audiveris.omr.constant.ConstantSet;
import org.audiveris.omr.glyph.Glyph;
import org.audiveris.omr.glyph.Shape;
import org.audiveris.omr.glyph.ShapeSet;
import org.audiveris.omr.glyph.ui.SymbolsEditor;
import org.audiveris.omr.sheet.Sheet;
import org.audiveris.omr.sheet.symbol.InterFactory;
import org.audiveris.omr.ui.Board;
import org.audiveris.omr.ui.OmrGlassPane;
import org.audiveris.omr.ui.dnd.AbstractGhostDropListener;
import org.audiveris.omr.ui.dnd.GhostDropAdapter;
import org.audiveris.omr.ui.dnd.GhostDropEvent;
import org.audiveris.omr.ui.dnd.GhostDropListener;
import org.audiveris.omr.ui.dnd.GhostGlassPane;
import org.audiveris.omr.ui.dnd.GhostMotionAdapter;
import org.audiveris.omr.ui.dnd.ScreenPoint;
import org.audiveris.omr.ui.selection.UserEvent;
import org.audiveris.omr.ui.symbol.MusicFont;
import org.audiveris.omr.ui.symbol.ShapeSymbol;
import org.audiveris.omr.ui.symbol.Symbols;
import org.audiveris.omr.ui.util.Panel;
import org.audiveris.omr.ui.view.RubberPanel;
import org.audiveris.omr.ui.view.ScrollView;
import org.audiveris.omr.ui.view.Zoom;
import org.audiveris.omr.util.Navigable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.SwingUtilities;
import org.audiveris.omr.sheet.ui.BookActions;

/**
 * Class {@code ShapeBoard} hosts a palette of shapes for insertion and assignment of
 * inter.
 * <p>
 * Shapes are gathered and presented in separate sets that are mutually exclusive.
 * <p>
 * A special set of shapes, always visible, is dedicated to the latest shapes used to ease the
 * repetition of user actions.
 * <ul>
 * <li>Direct insertion is performed by drag and drop to the target score view or sheet view</li>
 * <li>Assignment from an existing glyph is performed by a double-click</li>
 * </ul>
 * <p>
 * A few 2-char strings typed by the user trigger the selection of a shape and its assignment.
 *
 * @author <NAME>
 */
public class ShapeBoard
        extends Board
{

    private static final Constants constants = new Constants();

    private static final Logger logger = LoggerFactory.getLogger(ShapeBoard.class);

    /** To force the width of the various panels. */
    private static final int BOARD_WIDTH = 317;

    /**
     * To force the height of the various shape panels.
     * This is just a dirty hack, to force Swing FlowLayout to wrap its flow.
     * A better solution might be to use JGoodies Layout, when we have some time to migrate...
     */
    private static final Map<ShapeSet, Integer> heights = buildHeightMap();

    /** Map first typed char to selected shape set. */
    private static final Map<Character, ShapeSet> setMap = new HashMap<>();

    /** Map 2-char typed string to selected shape. */
    private static final Map<String, Shape> shapeMap = new HashMap<>();

    static {
        populateCharMaps();
    }

    /** Related sheet. */
    @Navigable(false)
    private final Sheet sheet;

    /** On-going DnD operation, if any. */
    private DndOperation dndOperation;

    /**
     * Called-back when a set is selected: the panel of shape sets is "replaced" by
     * the panel of shapes that compose the selected set.
     */
    private final ActionListener setListener = new ActionListener()
    {
        @Override
        public void actionPerformed (ActionEvent e)
        {
            String setName = ((JButton) e.getSource()).getName();
            ShapeSet set = ShapeSet.getShapeSet(setName);
            selectShapeSet(set);
        }
    };

    /**
     * Called-back when a panel of shapes is closed: the panel is replaced by the
     * panel of sets to allow the selection of another set.
     */
    private final ActionListener closeListener = new ActionListener()
    {
        @Override
        public void actionPerformed (ActionEvent e)
        {
            closeShapeSet();
        }
    };

    /**
     * Called-back when a shape button is (double-) clicked.
     */
    private final MouseListener mouseListener = new MouseAdapter()
    {
        // Ability to use the button for direct assignment via double-click
        @Override
        public void mouseClicked (MouseEvent e)
        {
            if (e.getClickCount() == 2) {
                Glyph glyph = sheet.getGlyphIndex().getSelectedGlyph();

                if (glyph != null) {
                    ShapeButton button = (ShapeButton) e.getSource();
                    assignGlyph(glyph, button.getShape());
                }
            }
        }
    };

    /** Panel of all shape sets. */
    private final Panel setsPanel;

    /** Map of shape panels, indexed by shapeSet. */
    private final Map<ShapeSet, Panel> shapesPanels = new HashMap<>();

    /** History of recently used shapes. */
    private final ShapeHistory shapeHistory;

    /** Current panel of shapes. */
    private Panel shapesPanel;

    /** GlassPane. */
    private final GhostGlassPane glassPane = OMR.gui.getGlassPane();

    /** Update image and forward mouse location. */
    private final MyMotionAdapter motionAdapter = new MyMotionAdapter();

    /** When symbol is dropped. */
    private final GhostDropListener<Shape> dropListener = new MyDropListener();

    /** When mouse is pressed (start) and released (stop). */
    private final MyDropAdapter dropAdapter = new MyDropAdapter();

    /** To handle sequence of keys typed. */
    private final MyKeyListener keyListener = new MyKeyListener();

    /**
     * Create a new ShapeBoard object.
     *
     * @param sheet    the related sheet
     * @param editor   symbols editor, needed for its view
     * @param selected true if initially selected
     */
    public ShapeBoard (Sheet sheet,
                       SymbolsEditor editor,
                       boolean selected)
    {
        super(Board.SHAPE, null, null, selected, false, false, false);
        this.sheet = sheet;

        dropAdapter.addDropListener(dropListener);
        shapeHistory = new ShapeHistory();
        setsPanel = buildSetsPanel();

        defineLayout();

        // Support for user shape keys
        editor.getView().addKeyListener(keyListener);
        getComponent().addKeyListener(keyListener);
    }

    //--------------//
    // addToHistory //
    //--------------//
    /**
     * Add a shape to recent history.
     *
     * @param shape the shape just used
     */
    public void addToHistory (Shape shape)
    {
        shapeHistory.add(shape);
    }

    //------------//
    // getHistory //
    //------------//
    /**
     * Report the recent shapes.
     *
     * @return list of most recent shapes
     */
    public List<Shape> getHistory ()
    {
        return shapeHistory.getShapes();
    }

    //---------//
    // onEvent //
    //---------//
    /**
     * Unused in this board.
     *
     * @param event unused
     */
    @Override
    public void onEvent (UserEvent event)
    {
        // Empty
    }

    //------------------//
    // populateCharMaps //
    //------------------//
    private static void populateCharMaps ()
    {
        char c;

        setMap.put(c = 'a', ShapeSet.Accidentals);
        shapeMap.put("" + c + 'f', Shape.FLAT);
        shapeMap.put("" + c + 'n', Shape.NATURAL);
        shapeMap.put("" + c + 's', Shape.SHARP);

        setMap.put(c = 'b', ShapeSet.BeamsAndTuplets);
        shapeMap.put("" + c + 'f', Shape.BEAM);
        shapeMap.put("" + c + 'h', Shape.BEAM_HOOK);
        shapeMap.put("" + c + '3', Shape.TUPLET_THREE);

        setMap.put(c = 'd', ShapeSet.Dynamics);
        shapeMap.put("" + c + 'p', Shape.DYNAMICS_P);
        shapeMap.put("" + c + 'm', Shape.DYNAMICS_MF);
        shapeMap.put("" + c + 'f', Shape.DYNAMICS_F);

        setMap.put(c = 'f', ShapeSet.Flags);
        shapeMap.put("" + c + 'u', Shape.FLAG_1_UP);
        shapeMap.put("" + c + 'd', Shape.FLAG_1);

        setMap.put(c = 'h', ShapeSet.HeadsAndDot);
        shapeMap.put("" + c + 'w', Shape.WHOLE_NOTE);
        shapeMap.put("" + c + 'v', Shape.NOTEHEAD_VOID);
        shapeMap.put("" + c + 'b', Shape.NOTEHEAD_BLACK);
        shapeMap.put("" + c + 'd', Shape.AUGMENTATION_DOT);

        setMap.put(c = 'r', ShapeSet.Rests);
        shapeMap.put("" + c + '1', Shape.WHOLE_REST);
        shapeMap.put("" + c + '2', Shape.HALF_REST);
        shapeMap.put("" + c + '4', Shape.QUARTER_REST);
        shapeMap.put("" + c + '8', Shape.EIGHTH_REST);

        setMap.put(c = 'p', ShapeSet.Physicals);
        shapeMap.put("" + c + 'l', Shape.LYRICS);
        shapeMap.put("" + c + 't', Shape.TEXT);
        shapeMap.put("" + c + 'a', Shape.SLUR_ABOVE);
        shapeMap.put("" + c + 'b', Shape.SLUR_BELOW);
        shapeMap.put("" + c + 's', Shape.STEM);
    }

    //------------//
    // addButtons //
    //------------//
    private void addButtons (Panel p,
                             List<Shape> shapes)
    {
        for (Shape shape : shapes) {
            // Preference for plain symbol
            ShapeSymbol symbol = Symbols.getSymbol(shape);

            if (symbol == null) {
                // Fall back to decorated symbol
                symbol = Symbols.getSymbol(shape, true);
            }

            if (symbol != null) {
                addButton(p, new ShapeButton(symbol));
            }
        }
    }

    //-----------//
    // addButton //
    //-----------//
    private void addButton (Panel panel,
                            ShapeButton button)
    {
        button.addMouseListener(mouseListener); // For double-click
        button.addMouseListener(dropAdapter); // For DnD transfer and double-click
        button.addMouseMotionListener(motionAdapter); // For dragging

        button.addKeyListener(keyListener);

        panel.add(button);
    }

    //-------------//
    // assignGlyph //
    //-------------//
    private void assignGlyph (Glyph glyph,
                              Shape shape)
    {
        // Actually assign the shape
        sheet.getInterController().assignGlyph(glyph, shape);
    }

    //----------------//
    // buildHeightMap //
    //----------------//
    private static Map<ShapeSet, Integer> buildHeightMap ()
    {
        Map<ShapeSet, Integer> map = new HashMap<>();
        map.put(ShapeSet.Accidentals, 40);
        map.put(ShapeSet.Articulations, 40);
        map.put(ShapeSet.Attributes, 60);
        map.put(ShapeSet.Barlines, 140);
        map.put(ShapeSet.BeamsAndTuplets, 60);
        map.put(ShapeSet.Clefs, 140);
        map.put(ShapeSet.Digits, 40);
        map.put(ShapeSet.Dynamics, 70);
        map.put(ShapeSet.Flags, 140);
        map.put(ShapeSet.Keys, 180);
        map.put(ShapeSet.Holds, 40);
        map.put(ShapeSet.Markers, 40);
        map.put(ShapeSet.HeadsAndDot, 60);
        map.put(ShapeSet.Ornaments, 70);
        map.put(ShapeSet.Physicals, 80);
        map.put(ShapeSet.Pluckings, 40);
        map.put(ShapeSet.Rests, 120);
        map.put(ShapeSet.Romans, 60);
        map.put(ShapeSet.Times, 120);

        return map;
    }

    //----------------//
    // buildSetsPanel //
    //----------------//
    /**
     * Build the global panel of sets.
     *
     * @return the global panel of sets
     */
    private Panel buildSetsPanel ()
    {
        Panel panel = new Panel();
        panel.setNoInsets();
        panel.setPreferredSize(new Dimension(BOARD_WIDTH, 160));

        FlowLayout layout = new FlowLayout();
        layout.setAlignment(FlowLayout.LEADING);
        panel.setLayout(layout);
        panel.setBackground(Color.LIGHT_GRAY);

        for (ShapeSet set : ShapeSet.getShapeSets()) {
            Shape rep = set.getRep();

            if (rep != null) {
                JButton button = new JButton();
                button.setIcon(rep.getDecoratedSymbol());
                button.setName(set.getName());
                button.addActionListener(setListener);
                button.setToolTipText(set.getName());
                button.setBorderPainted(false);
                panel.add(button);

                // Create the child shapesPanel
                shapesPanels.put(set, buildShapesPanel(set));
            }
        }

        panel.addKeyListener(keyListener);

        return panel;
    }

    //------------------//
    // buildShapesPanel //
    //------------------//
    /**
     * Build the panel of shapes for a given set.
     *
     * @param set the given set of shapes
     * @return the panel of shapes for the provided set
     */
    private Panel buildShapesPanel (ShapeSet set)
    {
        Panel panel = new Panel();
        panel.setNoInsets();
        panel.setPreferredSize(new Dimension(BOARD_WIDTH, getSetHeight(set)));

        FlowLayout layout = new FlowLayout();
        layout.setAlignment(FlowLayout.LEADING);
        panel.setLayout(layout);

        // Button to close this shapes panel and return to sets panel
        JButton close = new JButton(set.getName());
        close.addActionListener(closeListener);
        close.setToolTipText("Back to shape sets");
        close.setBorderPainted(false);
        panel.add(close);
        panel.addKeyListener(keyListener);

        // One button (or more) per shape
        addButtons(panel, set.getSortedShapes());

        return panel;
    }

    //---------------//
    // closeShapeSet //
    //---------------//
    private void closeShapeSet ()
    {
        // Hide current panel of shapes
        if (shapesPanel != null) {
            shapesPanel.setVisible(false);
        }

        // Show panel of sets
        setsPanel.setVisible(true);

        resizeBoard();<commit_msg>Addition of small button to go back to board of all families<commit_after>//------------------------------------------------------------------------------------------------//
//                                                                                                //
//                                      S h a p e B o a r d                                       //
//                                                                                                //
//------------------------------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr">
//
//  Copyright © Audiveris 2019. All rights reserved.
//
//  This program is free software: you can redistribute it and/or modify it under the terms of the
//  GNU Affero General Public License as published by the Free Software Foundation, either version
//  3 of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
//  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//  See the GNU Affero General Public License for more details.
//
//  You should have received a copy of the GNU Affero General Public License along with this
//  program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------------------------//
// </editor-fold>
package org.audiveris.omr.sig.ui;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

import org.audiveris.omr.OMR;
import org.audiveris.omr.constant.Constant;
import org.audiveris.omr.constant.ConstantSet;
import org.audiveris.omr.glyph.Glyph;
import org.audiveris.omr.glyph.Shape;
import org.audiveris.omr.glyph.ShapeSet;
import org.audiveris.omr.glyph.ui.SymbolsEditor;
import org.audiveris.omr.sheet.Sheet;
import org.audiveris.omr.sheet.symbol.InterFactory;
import org.audiveris.omr.ui.Board;
import org.audiveris.omr.ui.OmrGlassPane;
import org.audiveris.omr.ui.dnd.AbstractGhostDropListener;
import org.audiveris.omr.ui.dnd.GhostDropAdapter;
import org.audiveris.omr.ui.dnd.GhostDropEvent;
import org.audiveris.omr.ui.dnd.GhostDropListener;
import org.audiveris.omr.ui.dnd.GhostGlassPane;
import org.audiveris.omr.ui.dnd.GhostMotionAdapter;
import org.audiveris.omr.ui.dnd.ScreenPoint;
import org.audiveris.omr.ui.selection.UserEvent;
import org.audiveris.omr.ui.symbol.MusicFont;
import org.audiveris.omr.ui.symbol.ShapeSymbol;
import org.audiveris.omr.ui.symbol.Symbols;
import org.audiveris.omr.ui.util.Panel;
import org.audiveris.omr.ui.view.RubberPanel;
import org.audiveris.omr.ui.view.ScrollView;
import org.audiveris.omr.ui.view.Zoom;
import org.audiveris.omr.util.Navigable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import org.audiveris.omr.sheet.ui.BookActions;

/**
 * Class {@code ShapeBoard} hosts a palette of shapes for insertion and assignment of
 * inter.
 * <p>
 * Shapes are gathered and presented in separate sets that are mutually exclusive.
 * <p>
 * A special set of shapes, always visible, is dedicated to the latest shapes used to ease the
 * repetition of user actions.
 * <ul>
 * <li>Direct insertion is performed by drag and drop to the target score view or sheet view</li>
 * <li>Assignment from an existing glyph is performed by a double-click</li>
 * </ul>
 * <p>
 * A few 2-char strings typed by the user trigger the selection of a shape and its assignment.
 *
 * @author <NAME>
 */
public class ShapeBoard
        extends Board
{

    private static final Constants constants = new Constants();

    private static final Logger logger = LoggerFactory.getLogger(ShapeBoard.class);

    /** To force the width of the various panels. */
    private static final int BOARD_WIDTH = 317;

    /**
     * To force the height of the various shape panels.
     * This is just a dirty hack, to force Swing FlowLayout to wrap its flow.
     * A better solution might be to use JGoodies Layout, when we have some time to migrate...
     */
    private static final Map<ShapeSet, Integer> heights = buildHeightMap();

    /** Map first typed char to selected shape set. */
    private static final Map<Character, ShapeSet> setMap = new HashMap<>();

    /** Map 2-char typed string to selected shape. */
    private static final Map<String, Shape> shapeMap = new HashMap<>();

    static {
        populateCharMaps();
    }

    /** Related sheet. */
    @Navigable(false)
    private final Sheet sheet;

    /** On-going DnD operation, if any. */
    private DndOperation dndOperation;

    /**
     * Called-back when a set is selected: the panel of shape sets is "replaced" by
     * the panel of shapes that compose the selected set.
     */
    private final ActionListener setListener = new ActionListener()
    {
        @Override
        public void actionPerformed (ActionEvent e)
        {
            String setName = ((JButton) e.getSource()).getName();
            ShapeSet set = ShapeSet.getShapeSet(setName);
            selectShapeSet(set);
        }
    };

    /**
     * Called-back when a panel of shapes is closed: the panel is replaced by the
     * panel of sets to allow the selection of another set.
     */
    private final ActionListener closeListener = new ActionListener()
    {
        @Override
        public void actionPerformed (ActionEvent e)
        {
            closeShapeSet();
        }
    };

    /**
     * Called-back when a shape button is (double-) clicked.
     */
    private final MouseListener mouseListener = new MouseAdapter()
    {
        // Ability to use the button for direct assignment via double-click
        @Override
        public void mouseClicked (MouseEvent e)
        {
            if (e.getClickCount() == 2) {
                Glyph glyph = sheet.getGlyphIndex().getSelectedGlyph();

                if (glyph != null) {
                    ShapeButton button = (ShapeButton) e.getSource();
                    assignGlyph(glyph, button.getShape());
                }
            }
        }
    };

    /** Panel of all shape sets. */
    private final Panel setsPanel;

    /** Map of shape panels, indexed by shapeSet. */
    private final Map<ShapeSet, Panel> shapesPanels = new HashMap<>();

    /** History of recently used shapes. */
    private final ShapeHistory shapeHistory;

    /** Current panel of shapes. */
    private Panel shapesPanel;

    /** GlassPane. */
    private final GhostGlassPane glassPane = OMR.gui.getGlassPane();

    /** Update image and forward mouse location. */
    private final MyMotionAdapter motionAdapter = new MyMotionAdapter();

    /** When symbol is dropped. */
    private final GhostDropListener<Shape> dropListener = new MyDropListener();

    /** When mouse is pressed (start) and released (stop). */
    private final MyDropAdapter dropAdapter = new MyDropAdapter();

    /** To handle sequence of keys typed. */
    private final MyKeyListener keyListener = new MyKeyListener();

    /**
     * Create a new ShapeBoard object.
     *
     * @param sheet    the related sheet
     * @param editor   symbols editor, needed for its view
     * @param selected true if initially selected
     */
    public ShapeBoard (Sheet sheet,
                       SymbolsEditor editor,
                       boolean selected)
    {
        super(Board.SHAPE, null, null, selected, false, false, false);
        this.sheet = sheet;

        dropAdapter.addDropListener(dropListener);
        shapeHistory = new ShapeHistory();
        setsPanel = buildSetsPanel();

        defineLayout();

        // Support for user shape keys
        editor.getView().addKeyListener(keyListener);
        getComponent().addKeyListener(keyListener);
    }

    //--------------//
    // addToHistory //
    //--------------//
    /**
     * Add a shape to recent history.
     *
     * @param shape the shape just used
     */
    public void addToHistory (Shape shape)
    {
        shapeHistory.add(shape);
    }

    //------------//
    // getHistory //
    //------------//
    /**
     * Report the recent shapes.
     *
     * @return list of most recent shapes
     */
    public List<Shape> getHistory ()
    {
        return shapeHistory.getShapes();
    }

    //---------//
    // onEvent //
    //---------//
    /**
     * Unused in this board.
     *
     * @param event unused
     */
    @Override
    public void onEvent (UserEvent event)
    {
        // Empty
    }

    //------------------//
    // populateCharMaps //
    //------------------//
    private static void populateCharMaps ()
    {
        char c;

        setMap.put(c = 'a', ShapeSet.Accidentals);
        shapeMap.put("" + c + 'f', Shape.FLAT);
        shapeMap.put("" + c + 'n', Shape.NATURAL);
        shapeMap.put("" + c + 's', Shape.SHARP);

        setMap.put(c = 'b', ShapeSet.BeamsAndTuplets);
        shapeMap.put("" + c + 'f', Shape.BEAM);
        shapeMap.put("" + c + 'h', Shape.BEAM_HOOK);
        shapeMap.put("" + c + '3', Shape.TUPLET_THREE);

        setMap.put(c = 'd', ShapeSet.Dynamics);
        shapeMap.put("" + c + 'p', Shape.DYNAMICS_P);
        shapeMap.put("" + c + 'm', Shape.DYNAMICS_MF);
        shapeMap.put("" + c + 'f', Shape.DYNAMICS_F);

        setMap.put(c = 'f', ShapeSet.Flags);
        shapeMap.put("" + c + 'u', Shape.FLAG_1_UP);
        shapeMap.put("" + c + 'd', Shape.FLAG_1);

        setMap.put(c = 'h', ShapeSet.HeadsAndDot);
        shapeMap.put("" + c + 'w', Shape.WHOLE_NOTE);
        shapeMap.put("" + c + 'v', Shape.NOTEHEAD_VOID);
        shapeMap.put("" + c + 'b', Shape.NOTEHEAD_BLACK);
        shapeMap.put("" + c + 'd', Shape.AUGMENTATION_DOT);

        setMap.put(c = 'r', ShapeSet.Rests);
        shapeMap.put("" + c + '1', Shape.WHOLE_REST);
        shapeMap.put("" + c + '2', Shape.HALF_REST);
        shapeMap.put("" + c + '4', Shape.QUARTER_REST);
        shapeMap.put("" + c + '8', Shape.EIGHTH_REST);

        setMap.put(c = 'p', ShapeSet.Physicals);
        shapeMap.put("" + c + 'l', Shape.LYRICS);
        shapeMap.put("" + c + 't', Shape.TEXT);
        shapeMap.put("" + c + 'a', Shape.SLUR_ABOVE);
        shapeMap.put("" + c + 'b', Shape.SLUR_BELOW);
        shapeMap.put("" + c + 's', Shape.STEM);
    }

    //------------//
    // addButtons //
    //------------//
    private void addButtons (Panel p,
                             List<Shape> shapes)
    {
        for (Shape shape : shapes) {
            // Preference for plain symbol
            ShapeSymbol symbol = Symbols.getSymbol(shape);

            if (symbol == null) {
                // Fall back to decorated symbol
                symbol = Symbols.getSymbol(shape, true);
            }

            if (symbol != null) {
                addButton(p, new ShapeButton(symbol));
            }
        }
    }

    //-----------//
    // addButton //
    //-----------//
    private void addButton (Panel panel,
                            ShapeButton button)
    {
        button.addMouseListener(mouseListener); // For double-click
        button.addMouseListener(dropAdapter); // For DnD transfer and double-click
        button.addMouseMotionListener(motionAdapter); // For dragging

        button.addKeyListener(keyListener);

        panel.add(button);
    }

    //-------------//
    // assignGlyph //
    //-------------//
    private void assignGlyph (Glyph glyph,
                              Shape shape)
    {
        // Actually assign the shape
        sheet.getInterController().assignGlyph(glyph, shape);
    }

    //----------------//
    // buildHeightMap //
    //----------------//
    private static Map<ShapeSet, Integer> buildHeightMap ()
    {
        Map<ShapeSet, Integer> map = new HashMap<>();
        map.put(ShapeSet.Accidentals, 40);
        map.put(ShapeSet.Articulations, 40);
        map.put(ShapeSet.Attributes, 60);
        map.put(ShapeSet.Barlines, 140);
        map.put(ShapeSet.BeamsAndTuplets, 60);
        map.put(ShapeSet.Clefs, 140);
        map.put(ShapeSet.Digits, 40);
        map.put(ShapeSet.Dynamics, 70);
        map.put(ShapeSet.Flags, 140);
        map.put(ShapeSet.Keys, 180);
        map.put(ShapeSet.Holds, 40);
        map.put(ShapeSet.Markers, 40);
        map.put(ShapeSet.HeadsAndDot, 60);
        map.put(ShapeSet.Ornaments, 70);
        map.put(ShapeSet.Physicals, 80);
        map.put(ShapeSet.Pluckings, 40);
        map.put(ShapeSet.Rests, 120);
        map.put(ShapeSet.Romans, 60);
        map.put(ShapeSet.Times, 120);

        return map;
    }

    //----------------//
    // buildSetsPanel //
    //----------------//
    /**
     * Build the global panel of sets.
     *
     * @return the global panel of sets
     */
    private Panel buildSetsPanel ()
    {
        Panel panel = new Panel();
        panel.setNoInsets();
        panel.setPreferredSize(new Dimension(BOARD_WIDTH, 160));

        FlowLayout layout = new FlowLayout();
        layout.setAlignment(FlowLayout.LEADING);
        panel.setLayout(layout);
        panel.setBackground(Color.LIGHT_GRAY);

        for (ShapeSet set : ShapeSet.getShapeSets()) {
            Shape rep = set.getRep();

            if (rep != null) {
                JButton button = new JButton();
                button.setIcon(rep.getDecoratedSymbol());
                button.setName(set.getName());
                button.addActionListener(setListener);
                button.setToolTipText(set.getName());
                button.setBorderPainted(false);
                panel.add(button);

                // Create the child shapesPanel
                shapesPanels.put(set, buildShapesPanel(set));
            }
        }

        panel.addKeyListener(keyListener);

        return panel;
    }

    //------------------//
    // buildShapesPanel //
    //------------------//
    /**
     * Build the panel of shapes for a given set.
     *
     * @param set the given set of shapes
     * @return the panel of shapes for the provided set
     */
    private Panel buildShapesPanel (ShapeSet set)
    {
        Panel panel = new Panel();
        panel.setNoInsets();
        panel.setPreferredSize(new Dimension(BOARD_WIDTH, getSetHeight(set)));

        FlowLayout layout = new FlowLayout();
        layout.setAlignment(FlowLayout.LEADING);
        panel.setLayout(layout);

        // Button to close this family and return to all-families panel
        JButton close = new JButton("<");
        close.addActionListener(closeListener);
        close.setToolTipText("Back to all families");
        close.setBorderPainted(false); // To avoid visual confusion with draggable items
        panel.add(close);

        // Title for this family
        panel.add(new JLabel(set.getName()));

        // One button (or more) per shape
        addButtons(panel, set.getSortedShapes());

        // Specific listener for keyboard
        panel.addKeyListener(keyListener);

        return panel;
    }

    //---------------//
    // closeShapeSet //
    //---------------//
    private void closeShapeSet ()
    {
        // Hide current panel of shapes
        if (shapesPanel != null) {
            shapesPanel.setVisible(false);
        }

        // Show panel of sets
        setsPanel.setVisible(true);

        resizeBoard();
{'fraction_non_alphanumeric': 0.14979251817415992, 'fraction_numerical': 0.004180836791363764, 'mean_word_length': 1.9300667337050919, 'pattern_counts': {'":': 0, '<': 56, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2884435', 'n_tokens_mistral': 9442, 'n_tokens_neox': 8978, 'n_words': 2404}