//----------------------------------------------------------------------------// // // // S y m b o l s E d i t o r // // // //----------------------------------------------------------------------------// // <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.GlyphNetwork; import omr.glyph.Glyphs; import omr.glyph.Nest; import omr.glyph.ShapeEvaluator; import omr.glyph.facets.Glyph; import omr.glyph.ui.NestView.ItemRenderer; import omr.lag.Lag; import omr.lag.Section; import omr.lag.Sections; import omr.lag.ui.SectionBoard; import omr.run.RunBoard; import omr.score.entity.Measure; import omr.score.entity.ScoreSystem; import omr.score.entity.Slot; import omr.score.entity.SystemPart; import omr.score.ui.PageMenu; import omr.score.ui.PagePhysicalPainter; import omr.score.ui.PaintingParameters; import omr.selection.GlyphEvent; import omr.selection.GlyphSetEvent; import omr.selection.LocationEvent; import omr.selection.MouseMovement; import omr.selection.NestEvent; import omr.selection.SectionSetEvent; import static omr.selection.SelectionHint.*; import omr.selection.UserEvent; import omr.sheet.Sheet; import omr.sheet.SystemInfo; import omr.sheet.ui.BoundaryEditor; import omr.sheet.ui.PixelBoard; import omr.sheet.ui.SheetPainter; import omr.step.Step; import omr.ui.BoardsPane; import omr.ui.Colors; import omr.ui.PixelCount; import omr.ui.util.UIUtil; import omr.ui.view.ScrollView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; /** * Class {@code SymbolsEditor} defines, for a given sheet, a UI pane * from which all symbol processing actions can be launched and their * results checked. * * @author Hervé Bitteur */ public class SymbolsEditor implements PropertyChangeListener { //~ Static fields/initializers --------------------------------------------- /** Specific application parameters */ private static final Constants constants = new Constants(); /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(SymbolsEditor.class); //~ Instance fields -------------------------------------------------------- /** Related instance of symbols builder */ private final SymbolsController symbolsController; /** Related sheet */ private final Sheet sheet; /** BoundaryEditor companion */ private final BoundaryEditor boundaryEditor; /** Evaluator to check for NOISE glyphs */ private final ShapeEvaluator evaluator = GlyphNetwork.getInstance(); /** Related nest view */ private final MyView view; /** Popup menu related to page selection */ private PageMenu pageMenu; /** The entity used for display focus */ private ShapeFocusBoard focus; //~ Constructors ----------------------------------------------------------- //---------------// // SymbolsEditor // //---------------// /** * Create a view in the sheet assembly tabs, dedicated to the * display and handling of glyphs. * * @param sheet the sheet whose glyphs are considered * @param symbolsController the symbols controller for this sheet */ public SymbolsEditor (Sheet sheet, SymbolsController symbolsController) { this.sheet = sheet; this.symbolsController = symbolsController; sheet.setBoundaryEditor(boundaryEditor = new BoundaryEditor(sheet)); Nest nest = symbolsController.getNest(); view = new MyView(nest); view.setLocationService(sheet.getLocationService()); focus = new ShapeFocusBoard( sheet, symbolsController, new ActionListener() { @Override public void actionPerformed (ActionEvent e) { view.repaint(); } }, false); pageMenu = new PageMenu( sheet.getPage(), new SymbolMenu(symbolsController, evaluator, focus)); BoardsPane boardsPane = new BoardsPane( new PixelBoard(sheet), new RunBoard(sheet.getHorizontalLag(), false), new SectionBoard(sheet.getHorizontalLag(), false), new RunBoard(sheet.getVerticalLag(), false), new SectionBoard(sheet.getVerticalLag(), false), new SymbolGlyphBoard(symbolsController, true, true), focus, new EvaluationBoard(sheet, symbolsController, true), new ShapeBoard(sheet, symbolsController, false)); // Create a hosting pane for the view ScrollView slv = new ScrollView(view); sheet.getAssembly() .addViewTab(Step.DATA_TAB, slv, boardsPane); } //~ Methods ---------------------------------------------------------------- //-----------------// // addItemRenderer // //-----------------// /** * Register an items renderer to render items. * * @param renderer the additional renderer */ public void addItemRenderer (ItemRenderer renderer) { view.addItemRenderer(renderer); } //-----------// // highLight // //-----------// /** * Highlight the corresponding slot within the score display. * * @param slot the slot to highlight */ public void highLight (final Slot slot) { SwingUtilities.invokeLater( new Runnable() { @Override public void run () { view.highLight(slot); } }); } //-----------// // getSlotAt // //-----------// /** * Retrieve the measure slot closest to the provided point. * * @param point the provided point * @return the related slot, or null */ public Slot getSlotAt (Point point) { List<SystemInfo> systems = sheet.getSystems(); if (systems != null) { SystemInfo systemInfo = sheet.getSystemOf(point); if (systemInfo != null) { ScoreSystem system = systemInfo.getScoreSystem(); SystemPart part = system.getPartAt(point); Measure measure = part.getMeasureAt(point); if (measure != null) { return measure.getClosestSlot(point); } } } return null; } //----------------// // propertyChange // //----------------// @Override public void propertyChange (PropertyChangeEvent evt) { view.repaint(); } //---------// // refresh // //---------// /** * Refresh the UI display (reset the model values of all spinners, * update the colors of the glyphs). */ public void refresh () { view.refresh(); } //~ Inner Classes ---------------------------------------------------------- //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { //~ Instance fields ---------------------------------------------------- PixelCount measureMargin = new PixelCount( 10, "Number of pixels as margin when highlighting a measure"); } //--------// // MyView // //--------// private final class MyView extends NestView { //~ Instance fields ---------------------------------------------------- /** Currently highlighted slot, if any. */ private Slot highlightedSlot; //~ Constructors ------------------------------------------------------- private MyView (Nest nest) { super( nest, symbolsController, Arrays.asList(sheet.getHorizontalLag(), sheet.getVerticalLag())); setName("SymbolsEditor-MyView"); // Subscribe to all lags for SectionSet events for (Lag lag : lags) { lag.getSectionService() .subscribeStrongly(SectionSetEvent.class, this); } } //~ Methods ------------------------------------------------------------ // //------------// // pointAdded // //------------// @Override public void pointAdded (Point pt, MouseMovement movement) { // Cancel slot highlighting highLight(null); super.pointAdded(pt, movement); } //---------------// // pointSelected // //---------------// @Override public void pointSelected (Point pt, MouseMovement movement) { // Cancel slot highlighting highLight(null); super.pointSelected(pt, movement); } //--------------// // contextAdded // //--------------// @Override public void contextAdded (Point pt, MouseMovement movement) { if (!ViewParameters.getInstance().isSectionMode()) { // Glyph mode setFocusLocation(new Rectangle(pt), movement, CONTEXT_ADD); // Update highlighted slot if possible if (movement != MouseMovement.RELEASING) { highLight(getSlotAt(pt)); } } // Regardless of the selection mode (section or glyph) // we let the user play with the current glyph if so desired. Set<Glyph> glyphs = nest.getSelectedGlyphSet(); if (movement == MouseMovement.RELEASING) { if ((glyphs != null) && !glyphs.isEmpty()) { showPagePopup(pt); } } } //-----------------// // contextSelected // //-----------------// @Override public void contextSelected (Point pt, MouseMovement movement) { if (!ViewParameters.getInstance().isSectionMode()) { // Glyph mode setFocusLocation(new Rectangle(pt), movement, CONTEXT_INIT); // Update highlighted slot if possible if (movement != MouseMovement.RELEASING) { highLight(getSlotAt(pt)); } } if (movement == MouseMovement.RELEASING) { showPagePopup(pt); } } //-----------// // highLight // //-----------// /** * Make the provided slot stand out. * * @param slot the current slot or null */ public void highLight (Slot slot) { this.highlightedSlot = slot; repaint(); // To erase previous highlight // // Make the measure visible // // Safer // if ((measure == null) || (slot == null)) { // // return; // } // // ScoreSystem system = measure.getSystem(); // Dimension dimension = system.getDimension(); // Rectangle systemBox = new Rectangle(system.getTopLeft().x, // system.getTopLeft().y, dimension.width, // dimension.height // + system.getLastPart().getLastStaff().getHeight()); // // // Make the measure rectangle visible // Rectangle rect = measure.getBox(); // int margin = constants.measureMargin.getValue(); // // Actually, use the whole system height // rect.y = systemBox.y; // rect.height = systemBox.height; // rect.grow(margin, margin); // showFocusLocation(rect, false); } //---------// // onEvent // //---------// /** * On reception of SECTION_SET information, we build a * transient compound glyph which is then dispatched. * Such glyph is always generated (a null glyph if the set is null or * empty, a simple glyph if the set contains just one glyph, and a true * compound glyph when the set contains several glyphs) * * @param event the notified event */ @Override public void onEvent (UserEvent event) { try { // Ignore RELEASING if (event.movement == MouseMovement.RELEASING) { return; } // Default nest view behavior (locationEvent) super.onEvent(event); if (event instanceof LocationEvent) { // Location => Boundary handleEvent((LocationEvent) event); } else if (event instanceof SectionSetEvent) { // SectionSet => Compound handleEvent((SectionSetEvent) event); } } catch (Exception ex) { logger.warn(getClass().getName() + " onEvent error", ex); } } //--------// // render // //--------// @Override public void render (Graphics2D g) { PaintingParameters painting = PaintingParameters.getInstance(); if (painting.isInputPainting()) { // Should we draw the section borders? final boolean drawBorders = ViewParameters.getInstance() .isSectionMode(); // Stroke for borders final Stroke oldStroke = UIUtil.setAbsoluteStroke(g, 1f); if (lags != null) { for (Lag lag : lags) { // Render all sections, using assigned colors for (Section section : lag.getVertices()) { Glyph glyph = section.getGlyph(); if (focus.isDisplayed(glyph)) { section.render(g, drawBorders); } } } } // Restore stroke g.setStroke(oldStroke); } // Paint additional items, such as recognized items, etc... renderItems(g); } //---------// // publish // //---------// protected void publish (NestEvent event) { nest.getGlyphService() .publish(event); } //-------------// // renderItems // //-------------// @Override protected void renderItems (Graphics2D g) { PaintingParameters painting = PaintingParameters.getInstance(); if (painting.isInputPainting()) { // Render all sheet physical info known so far sheet.getPage() .accept( new SheetPainter(g, boundaryEditor.isSessionOngoing())); // Normal display of selected items super.renderItems(g); } if (painting.isOutputPainting()) { boolean mixed = painting.isInputPainting(); // Render the recognized score entities PagePhysicalPainter painter = new PagePhysicalPainter( g, mixed ? Colors.MUSIC_SYMBOLS : Colors.MUSIC_ALONE, mixed ? false : painting.isVoicePainting(), false, painting.isAnnotationPainting()); sheet.getPage() .accept(painter); // The slot being played, if any if (highlightedSlot != null) { painter.highlightSlot(highlightedSlot); } } } //-------------// // handleEvent // //-------------// /** * Interest in LocationEvent => system boundary modification? * * @param locationEvent location event */ @SuppressWarnings("unchecked") private void handleEvent (LocationEvent locationEvent) { super.onEvent(locationEvent); // Update system boundary? if ((locationEvent.hint == LOCATION_INIT) && boundaryEditor.isSessionOngoing()) { Rectangle rect = locationEvent.getData(); if ((rect != null) && (rect.width == 0) && (rect.height == 0)) { boundaryEditor.inspectBoundary(rect.getLocation()); } } } //-------------// // handleEvent // //-------------// /** * Interest in SectionSetEvent => transient Glyph. * * @param sectionSetEvent */ @SuppressWarnings("unchecked") private void handleEvent (SectionSetEvent sectionSetEvent) { if (!ViewParameters.getInstance() .isSectionMode()) { // Glyph selection mode return; } // Section selection mode MouseMovement movement = sectionSetEvent.movement; if (sectionSetEvent.hint.isLocation()) { // Collect section sets from all lags List<Section> allSections = new ArrayList<>(); for (Lag lag : lags) { Set<Section> selected = lag.getSelectedSectionSet(); if (selected != null) { allSections.addAll(selected); } } try { Glyph compound = null; if (!allSections.isEmpty()) { SystemInfo system = sheet.getSystemOfSections( allSections); if (system != null) { compound = system.buildTransientGlyph(allSections); } } logger.debug("Editor. Publish glyph {}", compound); publish( new GlyphEvent( this, GLYPH_TRANSIENT, movement, compound)); if (compound != null) { publish( new GlyphSetEvent( this, GLYPH_TRANSIENT, movement, Glyphs.sortedSet(compound))); } else { publish( new GlyphSetEvent( this, GLYPH_TRANSIENT, movement, null)); } } catch (IllegalArgumentException ex) { // All sections do not belong to the same system // No compound is allowed and displayed logger.warn( "Sections from different systems {}", Sections.toString(allSections)); } } } //---------------// // showPagePopup // //---------------// private void showPagePopup (Point pt) { pageMenu.updateMenu(new Point(pt.x, pt.y)); JPopupMenu popup = pageMenu.getPopup(); popup.show( this, getZoom().scaled(pt.x) + 20, getZoom().scaled(pt.y) + 30); } } }