//----------------------------------------------------------------------------//
// //
// B a s i c N e s 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.Main;
import omr.constant.Constant;
import omr.constant.ConstantSet;
import omr.glyph.facets.Glyph;
import omr.glyph.ui.ViewParameters;
import omr.lag.BasicRoi;
import omr.lag.Roi;
import omr.lag.Section;
import omr.lag.Sections;
import omr.math.Histogram;
import omr.run.Orientation;
import omr.selection.GlyphEvent;
import omr.selection.GlyphIdEvent;
import omr.selection.GlyphSetEvent;
import omr.selection.LocationEvent;
import omr.selection.MouseMovement;
import omr.selection.NestEvent;
import omr.selection.SelectionHint;
import static omr.selection.SelectionHint.*;
import omr.selection.SelectionService;
import omr.selection.UserEvent;
import omr.sheet.Sheet;
import omr.sheet.SystemInfo;
import omr.util.VipUtil;
import org.bushe.swing.event.EventSubscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Class {@code BasicNest} implements a {@link Nest}.
*
* @author Hervé Bitteur
*/
public class BasicNest
implements Nest,
EventSubscriber<UserEvent>
{
//~ Static fields/initializers ---------------------------------------------
/** Specific application parameters */
private static final Constants constants = new Constants();
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(BasicNest.class);
/** Events read on location service */
public static final Class<?>[] locEventsRead = new Class<?>[]{
LocationEvent.class};
/** Events read on nest (glyph) service */
public static final Class<?>[] glyEventsRead = new Class<?>[]{
GlyphIdEvent.class,
GlyphEvent.class,
GlyphSetEvent.class
};
//~ Instance fields --------------------------------------------------------
/** (Debug) a unique name for this nest. */
private final String name;
/** Related sheet. */
private final Sheet sheet;
/** Elaborated constants for this nest. */
private final Parameters params;
/**
* Smart glyph map, based on a physical glyph signature, and thus
* usable across several glyph extractions, to ensure glyph unicity
* whatever the sequential ID it is assigned.
*/
private final ConcurrentHashMap<GlyphSignature, Glyph> originals = new ConcurrentHashMap<>();
/**
* Collection of all glyphs ever inserted in this Nest, indexed by
* glyph id. No non-virtual glyph is ever removed from this map.
*/
private final ConcurrentHashMap<Integer, Glyph> allGlyphs = new ConcurrentHashMap<>();
/**
* Current map of section -> glyphs.
* This defines the glyphs that are currently active, since there is at
* least one section pointing to them (the sections collection is
* immutable).
* Nota: The glyph reference within the section is kept in sync
*/
private final ConcurrentHashMap<Section, Glyph> activeMap = new ConcurrentHashMap<>();
/**
* Collection of active glyphs.
* This is derived from the activeMap, to give direct access to all the
* active glyphs, and is kept in sync with activeMap.
* It also contains the virtual glyphs since these are always active.
*/
private Set<Glyph> activeGlyphs;
/** Collection of virtual glyphs. (with no underlying sections) */
private Set<Glyph> virtualGlyphs = new HashSet<>();
/** Global id to uniquely identify a glyph. */
private final AtomicInteger globalGlyphId = new AtomicInteger(0);
/** Location service (read & write). */
private SelectionService locationService;
/** Hosted glyph service. (Glyph, GlyphId and GlyphSet) */
protected final SelectionService glyphService;
//~ Constructors -----------------------------------------------------------
//-----------//
// BasicNest //
//-----------//
/**
* Create a glyph nest.
*
* @param name the distinguished name for this instance
*/
public BasicNest (String name,
Sheet sheet)
{
this.name = name;
this.sheet = sheet;
params = new Parameters();
glyphService = new SelectionService(name, Nest.eventsWritten);
}
//~ Methods ----------------------------------------------------------------
//----------//
// addGlyph //
//----------//
@Override
public Glyph addGlyph (Glyph glyph)
{
glyph = registerGlyph(glyph);
// Make absolutely all its sections point back to it
glyph.linkAllSections();
if (glyph.isVip()) {
logger.info("{} added", glyph.idString());
}
return glyph;
}
//--------//
// dumpOf //
//--------//
@Override
public String dumpOf (String title)
{
StringBuilder sb = new StringBuilder();
if (title != null) {
sb.append(String.format("%s%n", title));
}
// Dump of active glyphs
sb.append(String.format("Active glyphs (%s) :%n",
getActiveGlyphs().size()));
for (Glyph glyph : getActiveGlyphs()) {
sb.append(String.format("%s%n", glyph));
}
// Dump of inactive glyphs
Collection<Glyph> inactives = new ArrayList<>(getAllGlyphs());
inactives.removeAll(getActiveGlyphs());
sb.append(String.format("%nInactive glyphs (%s) :%n", inactives.size()));
for (Glyph glyph : inactives) {
sb.append(String.format("%s%n", glyph));
}
return sb.toString();
}
//-----------------//
// getActiveGlyphs //
//-----------------//
@Override
public synchronized Collection<Glyph> getActiveGlyphs ()
{
if (activeGlyphs == null) {
activeGlyphs = Glyphs.sortedSet(activeMap.values());
activeGlyphs.addAll(virtualGlyphs);
}
return Collections.unmodifiableCollection(activeGlyphs);
}
//--------------//
// getAllGlyphs //
//--------------//
@Override
public Collection<Glyph> getAllGlyphs ()
{
return Collections.unmodifiableCollection(allGlyphs.values());
}
//----------//
// getGlyph //
//----------//
@Override
public Glyph getGlyph (Integer id)
{
return allGlyphs.get(id);
}
//-----------------//
// getGlyphService //
//-----------------//
@Override
public SelectionService getGlyphService ()
{
return glyphService;
}
//--------------//
// getHistogram //
//--------------//
@Override
public Histogram<Integer> getHistogram (Orientation orientation,
Collection<Glyph> glyphs)
{
Histogram<Integer> histo = new Histogram<>();
if (!glyphs.isEmpty()) {
Rectangle box = Glyphs.getBounds(glyphs);
Roi roi = new BasicRoi(box);
histo = roi.getSectionHistogram(
orientation,
Glyphs.sectionsOf(glyphs));
}
return histo;
}
//---------//
// getName //
//---------//
@Override
public String getName ()
{
return name;
}
//-------------//
// getOriginal //
//-------------//
@Override
public Glyph getOriginal (Glyph glyph)
{
return getOriginal(glyph.getSignature());
}
//-------------//
// getOriginal //
//-------------//
@Override
public Glyph getOriginal (GlyphSignature signature)
{
// Find an old glyph registered with this signature
Glyph oldGlyph = originals.get(signature);
if (oldGlyph == null) {
return null;
}
// Check the old signature is still valid
if (oldGlyph.getSignature().compareTo(signature) == 0) {
return oldGlyph;
} else {
logger.debug("Obsolete signature for {}", oldGlyph);
return null;
}
}
//------------------//
// getSelectedGlyph //
//------------------//
@Override
public Glyph getSelectedGlyph ()
{
return (Glyph) getGlyphService().getSelection(GlyphEvent.class);
}
//---------------------//
// getSelectedGlyphSet //
//---------------------//
@SuppressWarnings("unchecked")
@Override
public Set<Glyph> getSelectedGlyphSet ()
{
return (Set<Glyph>) getGlyphService().getSelection(GlyphSetEvent.class);
}
//-------//
// isVip //
//-------//
@Override
public boolean isVip (Glyph glyph)
{
return params.vipGlyphs.contains(glyph.getId());
}
//--------------//
// lookupGlyphs //
//--------------//
@Override
public Set<Glyph> lookupGlyphs (Rectangle rect)
{
return Glyphs.lookupGlyphs(getActiveGlyphs(), rect);
}
//-------------------------//
// lookupIntersectedGlyphs //
//-------------------------//
@Override
public Set<Glyph> lookupIntersectedGlyphs (Rectangle rect)
{
return Glyphs.lookupIntersectedGlyphs(getActiveGlyphs(), rect);
}
//--------------------//
// lookupVirtualGlyph //
//--------------------//
@Override
public Glyph lookupVirtualGlyph (Point point)
{
for (Glyph virtual : virtualGlyphs) {
if (virtual.getBounds().contains(point)) {
return virtual;
}
}
return null;
}
//------------//
// mapSection //
//------------//
/**
* Map a section to a glyph, making the glyph active
*
* @param section the section to map
* @param glyph the assigned glyph
*/
@Override
public synchronized void mapSection (Section section,
Glyph glyph)
{
if (glyph != null) {
activeMap.put(section, glyph);
} else {
activeMap.remove(section);
}
// Invalidate the collection of active glyphs
activeGlyphs = null;
}
//---------//
// onEvent //
//---------//
@Override
public void onEvent (UserEvent event)
{
try {
// Ignore RELEASING
if (event.movement == MouseMovement.RELEASING) {
return;
}
if (event instanceof LocationEvent) {
// Location => enclosed Glyph(s) or 1 virtual glyph
handleEvent((LocationEvent) event);
} else if (event instanceof GlyphEvent) {
// Glyph => glyph contour & GlyphSet update
handleEvent((GlyphEvent) event);
} else if (event instanceof GlyphSetEvent) {
// GlyphSet => Compound glyph
handleEvent((GlyphSetEvent) event);
} else if (event instanceof GlyphIdEvent) {
// Glyph Id => Glyph
handleEvent((GlyphIdEvent) event);
}
} catch (Throwable ex) {
logger.warn(getClass().getName() + " onEvent error", ex);
}
}
//---------------//
// registerGlyph //
//---------------//
@Override
public Glyph registerGlyph (Glyph glyph)
{
// First check this physical glyph does not already exist
Glyph original = getOriginal(glyph);
if (original != null) {
if (original != glyph) {
// Reuse the existing glyph
if (logger.isDebugEnabled()) {
logger.debug("new avatar of #{}{}{}",
original.getId(),
Sections.
toString(" members", glyph.getMembers()),
Sections.toString(" original", original.
getMembers()));
}
glyph = original;
glyph.setPartOf(null);
}
} else {
GlyphSignature newSig = glyph.getSignature();
if (glyph.isTransient()) {
// Register with a brand new Id
final int id = generateId();
glyph.setId(id);
glyph.setNest(this);
allGlyphs.put(id, glyph);
if (isVip(glyph)) {
glyph.setVip();
}
} else {
// This is a re-registration
GlyphSignature oldSig = glyph.getRegisteredSignature();
if ((oldSig != null) && !newSig.equals(oldSig)) {
Glyph oldGlyph = originals.remove(oldSig);
if (oldGlyph != null) {
logger.debug("Updating registration of {} oldGlyph:{}",
glyph.idString(), oldGlyph.getId());
}
}
}
originals.put(newSig, glyph);
glyph.setRegisteredSignature(newSig);
logger.debug("Registered {} as original {}",
glyph.idString(), glyph.getSignature());
}
// Special for virtual glyphs
if (glyph.isVirtual()) {
virtualGlyphs.add(glyph);
}
return glyph;
}
//--------------------//
// removeVirtualGlyph //
//--------------------//
@Override
public synchronized void removeVirtualGlyph (VirtualGlyph glyph)
{
originals.remove(glyph.getSignature(), glyph);
allGlyphs.remove(glyph.getId(), glyph);
virtualGlyphs.remove(glyph);
activeGlyphs = null;
}
//-------------//
// setServices //
//-------------//
@Override
public void setServices (SelectionService locationService)
{
this.locationService = locationService;
for (Class<?> eventClass : locEventsRead) {
locationService.subscribeStrongly(eventClass, this);
}
for (Class<?> eventClass : glyEventsRead) {
glyphService.subscribeStrongly(eventClass, this);
}
}
//-------------//
// setServices //
//-------------//
@Override
public void cutServices (SelectionService locationService)
{
for (Class<?> eventClass : locEventsRead) {
locationService.unsubscribe(eventClass, this);
}
for (Class<?> eventClass : glyEventsRead) {
glyphService.unsubscribe(eventClass, this);
}
}
//----------//
// toString //
//----------//
@Override
public String toString ()
{
StringBuilder sb = new StringBuilder("{Nest");
sb.append(" ").append(name);
// Active/All glyphs
if (!allGlyphs.isEmpty()) {
sb.append(" glyphs=").append(getActiveGlyphs().size()).append("/").
append(allGlyphs.size());
} else {
sb.append(" noglyphs");
}
sb.append("}");
return sb.toString();
}
//---------//
// publish //
//---------//
/**
* Publish on glyph service
*
* @param event the event to publish
*/
protected void publish (NestEvent event)
{
glyphService.publish(event);
}
//---------//
// publish //
//---------//
/**
* Publish on location service
*
* @param event the event to publish
*/
protected void publish (LocationEvent event)
{
locationService.publish(event);
}
//------------------//
// subscribersCount //
//------------------//
/**
* Convenient method to retrieve the number of subscribers on the glyph
* service for a specific class
*
* @param classe the specific classe
* @return the number of subscribers interested in the specific class
*/
protected int subscribersCount (Class<? extends NestEvent> classe)
{
return glyphService.subscribersCount(classe);
}
//------------//
// generateId //
//------------//
private int generateId ()
{
return globalGlyphId.incrementAndGet();
}
//-------------//
// handleEvent //
//-------------//
/**
* Interest in sheet location => [active] glyph(s)
*
* @param locationEvent
*/
private void handleEvent (LocationEvent locationEvent)
{
SelectionHint hint = locationEvent.hint;
MouseMovement movement = locationEvent.movement;
Rectangle rect = locationEvent.getData();
if (!hint.isLocation() && !hint.isContext()) {
return;
}
if (rect == null) {
return;
}
if ((rect.width > 0) && (rect.height > 0)) {
// This is a non-degenerated rectangle
// Look for set of enclosed active glyphs
Set<Glyph> glyphsFound = lookupGlyphs(rect);
// Publish Glyph
Glyph glyph = glyphsFound.isEmpty() ? null
: glyphsFound.iterator().next();
publish(new GlyphEvent(this, hint, movement, glyph));
// Publish GlyphSet
publish(new GlyphSetEvent(this, hint, movement, glyphsFound));
} else {
// This is just a point
Glyph glyph = lookupVirtualGlyph(
new Point(rect.getLocation()));
// Publish virtual Glyph, if any
if (glyph != null) {
publish(new GlyphEvent(this, hint, movement, glyph));
} else {
// No virtual glyph found, a standard glyph is found by:
// Pt -> (h/v)run -> (h/v)section -> glyph
// So there is nothing to do here, except nullifying glyph
publish(new GlyphEvent(this, hint, movement, null));
// And let proper lag publish non-null glyph later
// Since BasicNest is first subscriber on location (berk!)
}
}
}
//-------------//
// handleEvent //
//-------------//
/**
* Interest in Glyph => glyph contour & GlyphSet update
*
* @param glyphEvent
*/
private void handleEvent (GlyphEvent glyphEvent)
{
SelectionHint hint = glyphEvent.hint;
MouseMovement movement = glyphEvent.movement;
Glyph glyph = glyphEvent.getData();
if ((hint == GLYPH_INIT) || (hint == GLYPH_MODIFIED)) {
// Display glyph contour
if (glyph != null) {
Rectangle box = glyph.getBounds();
publish(new LocationEvent(this, hint, movement, box));
}
}
// In glyph-selection mode, for non-transient glyphs
// (and only if we have interested subscribers)
if ((hint != GLYPH_TRANSIENT)
&& !ViewParameters.getInstance().isSectionMode()
&& (subscribersCount(GlyphSetEvent.class) > 0)) {
// Update glyph set
Set<Glyph> glyphs = getSelectedGlyphSet();
if (glyphs == null) {
glyphs = new LinkedHashSet<>();
}
if (hint == LOCATION_ADD) {
// Adding to (or Removing from) the set of glyphs
if (glyph != null) {
if (glyphs.contains(glyph)) {
glyphs.remove(glyph);
} else {
glyphs.add(glyph);
}
}
} else if (hint == CONTEXT_ADD) {
// Don't modify the set
} else {
// Overwriting the set of glyphs
if (glyph != null) {
// Make a one-glyph set
glyphs = Glyphs.sortedSet(glyph);
} else {
// Make an empty set
glyphs = Glyphs.sortedSet();
}
}
publish(new GlyphSetEvent(this, hint, movement, glyphs));
}
}
//-------------//
// handleEvent //
//-------------//
/**
* Interest in GlyphSet => Compound
*
* @param glyphSetEvent
*/
private void handleEvent (GlyphSetEvent glyphSetEvent)
{
if (ViewParameters.getInstance().isSectionMode()) {
// Section mode
return;
}
// Glyph mode
MouseMovement movement = glyphSetEvent.movement;
Set<Glyph> glyphs = glyphSetEvent.getData();
Glyph compound = null;
if ((glyphs != null) && (glyphs.size() > 1)) {
try {
SystemInfo system = sheet.getSystemOf(glyphs);
if (system != null) {
compound = system.buildTransientCompound(glyphs);
publish(
new GlyphEvent(
this,
SelectionHint.GLYPH_TRANSIENT,
movement,
compound));
}
} catch (IllegalArgumentException ex) {
// All glyphs do not belong to the same system
// No compound is allowed and displayed
logger.warn("Selecting glyphs from different systems");
}
}
}
//-------------//
// handleEvent //
//-------------//
/**
* Interest in Glyph ID => glyph
*
* @param glyphIdEvent
*/
private void handleEvent (GlyphIdEvent glyphIdEvent)
{
SelectionHint hint = glyphIdEvent.hint;
MouseMovement movement = glyphIdEvent.movement;
int id = glyphIdEvent.getData();
//TODO: Check the need for this:
// // Nullify Run entity
// publish(new RunEvent(this, hint, movement, null));
//
// // Nullify Section entity
// publish(new SectionEvent<GlyphSection>(this, hint, movement, null));
// Report Glyph entity (which may be null)
publish(new GlyphEvent(this, hint, movement, getGlyph(id)));
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// Constants //
//-----------//
private static final class Constants
extends ConstantSet
{
//~ Instance fields ----------------------------------------------------
Constant.String vipGlyphs = new Constant.String(
"",
"(Debug) Comma-separated list of VIP glyphs");
}
//------------//
// Parameters //
//------------//
/**
* Class {@code Parameters} gathers all constants related to nest
*/
private static class Parameters
{
//~ Instance fields ----------------------------------------------------
final List<Integer> vipGlyphs; // List of IDs for VIP glyphs
//~ Constructors -------------------------------------------------------
public Parameters ()
{
vipGlyphs = VipUtil.decodeIds(constants.vipGlyphs.getValue());
if (logger.isDebugEnabled()) {
Main.dumping.dump(this);
}
if (!vipGlyphs.isEmpty()) {
logger.info("VIP glyphs: {}", vipGlyphs);
}
}
}
}