//----------------------------------------------------------------------------//
// //
// B o u n d a r y 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.sheet.ui;
import omr.sheet.Sheet;
import omr.sheet.SystemBoundary;
import omr.sheet.SystemInfo;
import omr.step.Step;
import omr.step.Steps;
import omr.util.BrokenLine;
import omr.util.VerticalSide;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
/**
* Class {@code BoundaryEditor} handles the manual modification of
* systems boundaries.
*
* @author Hervé Bitteur
*/
public class BoundaryEditor
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(BoundaryEditor.class);
//~ Instance fields --------------------------------------------------------
//
/** Related sheet. */
private final Sheet sheet;
/** Concrete menu. */
private final JMenu menu;
/** Set of actions to update menu according to current selections. */
private final Collection<DynAction> dynActions = new HashSet<>();
/** Acceptable distance since last reference point. (while dragging) */
private final int maxDraggingDelta = BrokenLine.getDraggingDistance();
/** Ongoing modification session?. */
private boolean sessionOngoing = false;
/** Set of modified lines in an edition session. */
private Set<BrokenLine> modifiedLines = new HashSet<>();
/** Designated reference point, if any. */
private Point currentPoint = null;
/** Line containing the currentPoint, if any. */
private BrokenLine currentLine = null;
/**
* One of the two systems that contain the current line, if any.
* We don't need to know which one it is precisely.
*/
private SystemInfo currentSystem = null;
//~ Constructors -----------------------------------------------------------
//
//----------------//
// BoundaryEditor //
//----------------//
/**
* Creates a new BoundaryEditor object.
*/
public BoundaryEditor (Sheet sheet)
{
this.sheet = sheet;
menu = new JMenu("Boundaries ...");
menu.add(new JMenuItem(new StartAction()));
menu.add(new JMenuItem(new StopAction()));
updateMenu();
}
//~ Methods ----------------------------------------------------------------
//---------//
// getMenu //
//---------//
public JMenu getMenu ()
{
return menu;
}
//-----------------//
// inspectBoundary //
//-----------------//
/**
* Try to update a system limit.
* Note that systems limit lines are modified online but subsequent
* updates on system content (glyph, etc) will not be performed before
* user explicitly asks for completion, which triggers an asynchronous
* update of system/staff content and reprocessing of steps.
*
* @param pt Current location of user mouse
*/
public void inspectBoundary (Point pt)
{
if (!sessionOngoing) {
return;
}
if (currentPoint != null) {
// Are we close enough to the current reference point?
Rectangle rect = new Rectangle(currentPoint);
rect.grow(maxDraggingDelta, maxDraggingDelta);
if (!rect.contains(pt)) {
currentPoint = null;
}
}
if (currentPoint == null) {
// Are we close enough to any existing reference point?
currentSystem = sheet.getSystemsNear(pt).iterator().next();
for (BrokenLine line : currentSystem.getBoundary().getLimits()) {
currentPoint = line.findPoint(pt);
if (currentPoint != null) {
currentLine = line;
break;
}
}
}
if (currentPoint != null) {
// Move the current reference point to user pt
currentPoint.setLocation(pt);
modifiedLines.add(currentLine);
// If now we get colinear segments, let's merge them
if (currentLine.isColinear(currentPoint)) {
currentLine.removePoint(currentPoint);
currentPoint = null;
}
updateSystemPair();
} else {
// Are we close to a segment, to define a new ref point?
currentSystem = sheet.getSystemsNear(pt).iterator().next();
for (BrokenLine line : currentSystem.getBoundary().getLimits()) {
Point segmentStart = line.findSegment(pt);
if (segmentStart != null) {
// Add a new ref point
currentPoint = pt;
currentLine = line;
line.insertPointAfter(pt, segmentStart);
modifiedLines.add(currentLine);
updateSystemPair();
break;
}
}
}
}
//------------------//
// isSessionOngoing //
//------------------//
public boolean isSessionOngoing ()
{
return sessionOngoing;
}
//------------//
// updateMenu //
//------------//
public final void updateMenu ()
{
// Enable the menu only once SYSTEMS has been performed
Step split = Steps.valueOf(Steps.SYSTEMS);
menu.setEnabled(split.isDone(sheet));
// Enable actions according to current session status
for (DynAction action : dynActions) {
action.update();
}
}
//------------------//
// updateSystemPair //
//------------------//
/**
* Update the current system, as well as the other system (if any)
* which shares the currentLine with the provided system.
*/
private void updateSystemPair ()
{
// Update lastSystem
currentSystem.updateBoundary();
// Find other system, if any, sharing this currentLine
List<SystemInfo> systems = sheet.getSystems();
int sysIdx = currentSystem.getId() - 1;
SystemBoundary boundary = currentSystem.getBoundary();
SystemInfo sharingSystem = null;
if (currentLine == boundary.getLimit(VerticalSide.BOTTOM)) {
// Sharing system is the following system, if any
if (sysIdx < (systems.size() - 1)) {
sharingSystem = systems.get(sysIdx + 1);
}
} else {
// Sharing system is the preceding system, if any
if (sysIdx > 0) {
sharingSystem = systems.get(sysIdx - 1);
}
}
if (sharingSystem != null) {
// Update sharing system
sharingSystem.updateBoundary();
}
// Update user display
sheet.getSymbolsEditor().refresh();
}
//~ Inner Classes ----------------------------------------------------------
//-----------//
// DynAction //
//-----------//
/**
* Base implementation, to register the dynamic actions that need
* to be updated according to the current context.
*/
public abstract class DynAction
extends AbstractAction
{
//~ Constructors -------------------------------------------------------
public DynAction ()
{
// Record the instance
dynActions.add(this);
}
//~ Methods ------------------------------------------------------------
public abstract void update ();
}
//-------------//
// StartAction //
//-------------//
private class StartAction
extends DynAction
{
//~ Constructors -------------------------------------------------------
public StartAction ()
{
putValue(NAME, "Start edition");
putValue(SHORT_DESCRIPTION, "Start boundaries edition");
}
//~ Methods ------------------------------------------------------------
@Override
public void actionPerformed (ActionEvent e)
{
logger.info("Boundaries edition started...");
currentPoint = null;
currentLine = null;
modifiedLines.clear();
sessionOngoing = true;
// Highlight border lines
sheet.getSymbolsEditor().refresh();
}
@Override
public void update ()
{
setEnabled(!sessionOngoing);
}
}
//------------//
// StopAction //
//------------//
private class StopAction
extends DynAction
{
//~ Constructors -------------------------------------------------------
public StopAction ()
{
putValue(NAME, "Complete edition");
putValue(SHORT_DESCRIPTION, "Complete ongoing edition");
}
//~ Methods ------------------------------------------------------------
@Override
public void actionPerformed (ActionEvent e)
{
if (!modifiedLines.isEmpty()) {
// At least, one limit line has been modified
logger.info("Completing boundaries edition...");
sheet.getSymbolsController()
.asyncModifyBoundaries(modifiedLines);
} else {
logger.info("No boundary modified");
}
sessionOngoing = false;
// De-highlight border lines
sheet.getSymbolsEditor().refresh();
}
@Override
public void update ()
{
setEnabled(sessionOngoing);
}
}
}