//----------------------------------------------------------------------------// // // // S h e e t s C o n t r o l l e 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.constant.Constant; import omr.constant.ConstantSet; import omr.score.Score; import omr.score.entity.Page; import omr.selection.SelectionService; import omr.selection.SheetEvent; import omr.sheet.Sheet; import org.bushe.swing.event.EventSubscriber; import org.jdesktop.application.Action; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import javax.swing.JComponent; import javax.swing.JTabbedPane; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * Class {@code SheetsController} is the UI Controller in charge of * user interactions with the sheets. * * <p>Multiple sheets are handled by means of a tabbed pane. For each tab, and * thus for each sheet, we have a separate {@link SheetAssembly}. All methods * that access these shared entities (tabbedPane, assemblies) are synchronized. * </p> * * <p>This class encapsulates an event service, which publishes the sheet * currently selected by a user interface. See {@link #subscribe}, * {@link #unsubscribe} and {@link #getSelectedSheet}.</p> * * <p>This class is meant to be a Singleton</p> * * @author Hervé Bitteur */ public class SheetsController implements ChangeListener { //~ Static fields/initializers --------------------------------------------- /** Specific application parameters */ private static final Constants constants = new Constants(); /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger( SheetsController.class); /** Events that can be published on sheet service */ private static final Class<?>[] eventsWritten = new Class<?>[]{ SheetEvent.class}; /** The single instance of this class */ private static volatile SheetsController INSTANCE; //~ Instance fields -------------------------------------------------------- /** Ordered sequence of sheet assemblies */ private final ArrayList<SheetAssembly> assemblies; /** The concrete tabbed pane, one tab per sheet */ private final JTabbedPane tabbedPane; /** * The global event service dedicated to publication of the * currently selected sheet. */ private final SelectionService sheetService = new SelectionService( getClass().getSimpleName(), eventsWritten); //~ Constructors ----------------------------------------------------------- //------------------// // SheetsController // //------------------// /** * Create the SheetsController singleton. */ private SheetsController () { tabbedPane = new JTabbedPane(); assemblies = new ArrayList<>(); // Listener on sheet tab operations tabbedPane.addChangeListener(this); } //~ Methods ---------------------------------------------------------------- //----------------// // callAboutSheet // //----------------// /** * Call the attention about the provided sheet, by publishing it on * the proper event service. * * @param sheet the provided sheet, which may be null */ public void callAboutSheet (Sheet sheet) { sheetService.publish(new SheetEvent(this, null, null, sheet)); } //----------------// // createAssembly // //----------------// /** * Create the assembly that relates to the specified sheet. * * @param sheet the sheet to be viewed (sheet cannot be null). * @return the created assembly */ public synchronized SheetAssembly createAssembly (Sheet sheet) { logger.debug("createAssembly {}", sheet.getId()); // Create the assembly on this sheet SheetAssembly assembly = new SheetAssembly(sheet); // Initial zoom ratio assembly.setZoomRatio(constants.initialZoomRatio.getValue()); // Make sure the assembly is part of the tabbed pane int sheetIndex = tabbedPane.indexOfComponent(assembly.getComponent()); if (sheetIndex == -1) { logger.debug("Adding assembly for sheet {}", sheet.getId()); // Insert in tabbed pane assemblies.add(assembly); JComponent comp = assembly.getComponent(); tabbedPane.addTab( defineTitleFor(sheet), null, comp, sheet.getScore().getImagePath()); } return assembly; } //-------------------// // dumpAllAssemblies // //-------------------// @Action public synchronized void dumpAllAssemblies () { for (SheetAssembly assembly : assemblies) { logger.info("Assembly of {} {}", assembly.getSheet(), assembly); } } //--------------------------// // dumpCurrentSheetServices // //--------------------------// /** * Debug action to dump the current status of all event services * related to the selected sheet if any. */ public void dumpCurrentSheetServices () { Sheet sheet = getSelectedSheet(); logger.info("Sheet:{}", sheet); if (sheet == null) { return; } sheet.getLocationService().dumpSubscribers(); if (sheet.getHorizontalLag() != null) { sheet.getHorizontalLag().getSectionService().dumpSubscribers(); sheet.getHorizontalLag().getRunService().dumpSubscribers(); } if (sheet.getVerticalLag() != null) { sheet.getVerticalLag().getSectionService().dumpSubscribers(); sheet.getVerticalLag().getRunService().dumpSubscribers(); } sheet.getNest().getGlyphService().dumpSubscribers(); } //--------------// // getComponent // //--------------// /** * Give access to the real pane (to insert in proper UI hierarchy). * * @return the concrete component */ public JComponent getComponent () { return tabbedPane; } //-----------------// // getCurrentSheet // //-----------------// /** * A convenient static method to directly report the currently * selected sheet, if any. * * @return the selected sheet, or null */ public static Sheet getCurrentSheet () { return getInstance().getSelectedSheet(); } //-------------// // getInstance // //-------------// /** * Report the single instance of this class. * * @return the single instance */ public static SheetsController getInstance () { if (INSTANCE == null) { INSTANCE = new SheetsController(); } return INSTANCE; } //------------------// // getSelectedSheet // //------------------// /** * Convenient method to directly access currently selected sheet, * if any. * * @return the selected sheet, which may be null (if no sheet is selected) */ public Sheet getSelectedSheet () { SheetEvent sheetEvent = (SheetEvent) sheetService.getLastEvent( SheetEvent.class); return (sheetEvent != null) ? sheetEvent.getData() : null; } //----------------// // removeAssembly // //----------------// /** * Remove the specified view from the tabbed pane. * * @param sheet the sheet to close */ public synchronized void removeAssembly (Sheet sheet) { SheetAssembly assembly = sheet.getAssembly(); int sheetIndex = tabbedPane.indexOfComponent( assembly.getComponent()); if (sheetIndex != -1) { logger.debug("Removing assembly {}", sheet); // Let others know (if this closing sheet was the current one) if (sheet == getSelectedSheet()) { callAboutSheet(null); } // Remove from assemblies assemblies.remove(sheetIndex); // Remove from tabs tabbedPane.remove(sheetIndex); Score score = sheet.getScore(); // Make sure the first sheet of a multipage score is OK // We need to modify the tab label for the score (new) first tab if (!score.getPages().isEmpty()) { Page firstPage = (Page) score.getPages().get(0); Sheet firstSheet = firstPage.getSheet(); int firstIndex = tabbedPane.indexOfComponent( firstSheet.getAssembly().getComponent()); if (firstIndex != -1) { tabbedPane.setTitleAt( firstIndex, defineTitleFor(firstSheet)); } } } } //--------------// // showAssembly // //--------------// /** * Display the assembly that relates to the specified sheet. * * @param sheet the sheet to be viewed (sheet cannot be null). */ public synchronized void showAssembly (Sheet sheet) { logger.debug("showAssembly {}", sheet.getId()); if (sheet != null) { SheetAssembly assembly = sheet.getAssembly(); // Make sure the assembly is part of the tabbed pane int sheetIndex = tabbedPane.indexOfComponent( assembly.getComponent()); if (sheetIndex != -1) { tabbedPane.setSelectedIndex(sheetIndex); } else { logger.warn("No tab found for {}", sheet); } } } //--------------// // stateChanged // //--------------// /** * This method is called whenever the sheet selection is modified, * whether programmatically (by means of {@link #showAssembly}) * or by user action (manual selection of the sheet tab). * * <p> Set the state (enabled or disabled) of all menu items that depend on * status of current sheet. */ @Override public synchronized void stateChanged (ChangeEvent e) { if (e.getSource() == tabbedPane) { final int sheetIndex = tabbedPane.getSelectedIndex(); // User has selected a new sheet tab? if (sheetIndex != -1) { // Connect the new sheet tab sheetTabSelected(sheetIndex); } } } //-----------// // subscribe // //-----------// /** * Subscribe to the sheet event service (for the SheetEvent class). * * @param subscriber The subscriber to accept the events when published. */ public void subscribe (EventSubscriber<SheetEvent> subscriber) { sheetService.subscribeStrongly(SheetEvent.class, subscriber); } //-------------// // unsubscribe // //-------------// /** * Unsubscribe to the sheet event service (for the SheetEvent class). * * @param subscriber the entity to unsubscribe */ public void unsubscribe (EventSubscriber<SheetEvent> subscriber) { sheetService.unsubscribe(SheetEvent.class, subscriber); } //----------------// // defineTitleFor // //----------------// /** * Generate proper tab title for the provided sheet. * * @param sheet the provided sheet instance * @return the title to use for the related tab */ private String defineTitleFor (Sheet sheet) { Page page = sheet.getPage(); Score score = page.getScore(); int index = page.getIndex(); if (score.isMultiPage()) { if (page == score.getFirstPage()) { return score.getRadix() + "#" + index; } else { return "#" + index; } } else { if (index != 1) { return score.getRadix() + "#" + index; } else { return score.getRadix(); } } } //------------------// // sheetTabSelected // //------------------// /** * Run when a sheetTab has been selected in the tabbedPane. * * @param sheetIndex the index of the tab */ private void sheetTabSelected (int sheetIndex) { // Remember the new selected sheet SheetAssembly assembly = assemblies.get(sheetIndex); Sheet sheet = assembly.getSheet(); // Tell everyone about the new selected sheet callAboutSheet(sheet); // Tell the selected assembly that it now has the focus... assembly.assemblySelected(); } //~ Inner Classes ---------------------------------------------------------- //-----------// // Constants // //-----------// private static final class Constants extends ConstantSet { //~ Instance fields ---------------------------------------------------- Constant.Ratio initialZoomRatio = new Constant.Ratio( 0.5, "Initial zoom ratio for displayed sheet pictures"); } }