//----------------------------------------------------------------------------// // // // S h e e t A s s e m b l y // // // //----------------------------------------------------------------------------// // <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.Main; import omr.selection.LocationEvent; import omr.sheet.Sheet; import omr.step.Step; import omr.ui.Board; import omr.ui.BoardsPane; import omr.ui.GuiActions; import omr.ui.MainGui; import omr.ui.util.Panel; import omr.ui.util.UIUtil; import omr.ui.view.LogSlider; import omr.ui.view.Rubber; import omr.ui.view.RubberPanel; import omr.ui.view.ScrollView; import omr.ui.view.Zoom; import org.bushe.swing.event.EventService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Rectangle; import java.util.HashMap; import java.util.Map; import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * Class {@code SheetAssembly} is a UI assembly dedicated to the * display of one sheet. * * It gathers: <ul> * <li>a {@link Zoom} with its dedicated graphical {@link LogSlider}</li> * <li>a mouse adapter {@link Rubber}</li> * <li>a tabbed pane of {@link ScrollView}'s for all views of this sheet</li> * </ul> * * <p>Although not part of the same Swing container, the SheetAssembly also * refers to a sequence of {@link BoardsPane}'s which is parallel to the * sequence of ScrollView's. * * @author Hervé Bitteur */ public class SheetAssembly implements ChangeListener { //~ Static fields/initializers --------------------------------------------- /** Usual logger utility */ private static final Logger logger = LoggerFactory.getLogger(SheetAssembly.class); //~ Instance fields -------------------------------------------------------- // /** Link with sheet. */ private final Sheet sheet; /** Service of sheetlocation. */ private final EventService locationService; /** The concrete UI component. */ private Panel component = new Panel(); /** To manually control the zoom ratio. */ private final LogSlider slider = new LogSlider( 2, 5, LogSlider.VERTICAL, -3, 4, 0); /** Tabbed container for all views of the sheet. */ private final JTabbedPane viewsPane = new JTabbedPane(); /** Zoom, with default ratio set to 1. */ private final Zoom zoom = new Zoom(slider, 1); /** Mouse adapter. */ private final Rubber rubber = new Rubber(zoom); /** Map: scrollPane -> view tab. */ private final Map<JScrollPane, ViewTab> tabs = new HashMap<>(); /** Previously selected tab. */ private ViewTab previousTab = null; //~ Constructors ----------------------------------------------------------- //---------------// // SheetAssembly // //---------------// /** * Create a new {@code SheetAssembly} instance dedicated to one sheet. * * @param sheet the related sheet */ public SheetAssembly (Sheet sheet) { logger.debug("creating SheetAssembly on {}", sheet); this.sheet = sheet; // Service for sheet location events locationService = sheet.getLocationService(); // GUI stuff slider.setToolTipText("Adjust Zoom Ratio"); // To be notified of view selection (manually or programmatically) viewsPane.addChangeListener(this); logger.debug("SheetAssembly created."); defineLayout(); } //~ Methods ---------------------------------------------------------------- //----------// // addBoard // //----------// /** * Add a board into the BoardsPane corresponding to the provided * view tab title. * * @param title the title of the targeted view tab * @param board the board to add dynamically */ public void addBoard (String title, Board board) { JScrollPane pane = getPane(title); if (pane == null) { logger.warn("Unknown tab {}", title); } else { ViewTab viewTab = tabs.get(pane); ///logger.warn("Adding " + board + " to " + title); viewTab.boardsPane.addBoard(board); } } //------------// // addViewTab // //------------// /** * Add a new tab, that contains a new view on the sheet. * * @param label the label to use for the tab * @param sv the view on the sheet * @param boardsPane the board pane associated to the tab */ public void addViewTab (String label, ScrollView sv, BoardsPane boardsPane) { JScrollPane scroll = sv.getComponent(); UIUtil.suppressBorders(scroll); if (boardsPane != null) { boardsPane.setName(label); } logger.debug("addViewTab begin {} boardsPane={} comp=@{}", label, boardsPane, Integer.toHexString(scroll.hashCode())); // Remove any existing viewTab with the same label for (ViewTab tab : tabs.values()) { if (tab.title.equals(label)) { tab.remove(); break; } } // Register the component tabs.put(scroll, new ViewTab(label, boardsPane, sv)); // Actually insert the related Swing tab viewsPane.addTab(label, scroll); // Select this new tab viewsPane.setSelectedComponent(scroll); logger.debug("addViewTab end {} boardsPane={}", label, boardsPane); } //------------------// // assemblySelected // //------------------// /** * Method called when this sheet assembly is selected. * (we can have several sheets displayed, each with its sheet assembly). * This is called from {@link omr.sheet.ui.SheetsController} when the tab * of another sheet is selected. */ public void assemblySelected () { logger.debug("{} assemblySelected", sheet.getId()); // Display the related boards displayBoards(); // Display the errors pane of this assembly? if (GuiActions.getInstance().isErrorsDisplayed()) { Main.getGui().showErrors(getErrorsPane()); } } //-------// // close // //-------// /** * Close the assembly, by removing it from the containing sheet * tabbed pane. */ public void close () { MainGui gui = Main.getGui(); gui.removeBoardsPane(); // Disconnect all keyboard bindings from PixelBoard's (as a workaround // for a Swing memory leak) for (ViewTab tab : tabs.values()) { tab.disconnectKeyboard(); } tabs.clear(); // Useful ??? // Hide the error messages (for this sheet) Main.getGui().hideErrors(sheet.getErrorsEditor().getComponent()); } //--------------// // getComponent // //--------------// /** * Report the UI component. * * @return the concrete component */ public JComponent getComponent () { return component; } //-----------------// // getSelectedView // //-----------------// /** * Report the tabbed view currently selected. * * @return the current tabbed view */ public ScrollView getSelectedView () { ViewTab currentTab = getCurrentViewTab(); if (currentTab != null) { return currentTab.scrollView; } else { return null; } } //----------// // getSheet // //----------// /** * Report the sheet this assembly is related to. * * @return the related sheet */ public Sheet getSheet () { return sheet; } //---------------// // selectViewTab // //---------------// /** * Force a tab selection programmatically. * * @param step the step whose related tab must be selected */ public void selectViewTab (Step step) { final String title = step.getTab(); for (int i = 0, count = viewsPane.getTabCount(); i < count; i++) { if (viewsPane.getTitleAt(i).equals(title)) { viewsPane.setSelectedIndex(i); viewsPane.repaint(); logger.debug("Selected view tab {}", title); return; } } // Currently, there is no view tab displayed for this step } //--------------// // setZoomRatio // //--------------// /** * Modify the ratio of the global zoom for all views of the sheet. * * @param ratio the new display ratio */ public void setZoomRatio (double ratio) { zoom.setRatio(ratio); } //--------------// // stateChanged // //--------------// /** * This method is called whenever another view tab is selected * in the SheetAssembly (or when a tab is removed). * * @param e the originating change event (not used) */ @Override public void stateChanged (ChangeEvent e) { ViewTab currentTab = getCurrentViewTab(); logger.debug("SheetAssembly stateChanged previousTab:{} currentTab:{}", previousTab, currentTab); if (currentTab != previousTab) { if (previousTab != null) { previousTab.deselected(); } if (currentTab != null) { currentTab.selected(); } } previousTab = currentTab; } //--------------// // defineLayout // //--------------// /** * Define the layout of this assembly. */ private void defineLayout () { component.setLayout(new BorderLayout()); component.setNoInsets(); component.add(slider, BorderLayout.WEST); component.add(viewsPane, BorderLayout.CENTER); } //---------------// // displayBoards // //---------------// /** * Make the boards pane visible (for this sheet & view). */ private void displayBoards () { // Make sure the view tab is ready JScrollPane comp = (JScrollPane) viewsPane.getSelectedComponent(); if (comp != null) { // Retrieve the proper boards pane, if any ViewTab tab = tabs.get(comp); if (tab != null) { tab.displayBoards(); } } } //-------------------// // getCurrentViewTab // //-------------------// /** * Report the ViewTab currently selected, if any. * * @return the current ViewTab, or null */ private ViewTab getCurrentViewTab () { JScrollPane comp = (JScrollPane) viewsPane.getSelectedComponent(); if (comp != null) { return tabs.get(comp); } else { return null; } } //---------------// // getErrorsPane // //---------------// /** * Report the UI pane dedicated to the current errors. * * @return the errors pane */ private JComponent getErrorsPane () { return sheet.getErrorsEditor().getComponent(); } //---------// // getPane // //---------// /** * Find the view that corresponds to the provided tab title. * * @param title the tab title. * @return the view found, or null */ private JScrollPane getPane (String title) { for (int i = 0, count = viewsPane.getTabCount(); i < count; i++) { if (viewsPane.getTitleAt(i).equals(title)) { return (JScrollPane) viewsPane.getComponentAt(i); } } return null; } //~ Inner Classes ---------------------------------------------------------- //---------// // ViewTab // //---------// /** * A simple structure to gather the various aspects of a view tab. * All instances are kept in the {@link SheetAssembly#tabs} map. */ private class ViewTab { //~ Instance fields ---------------------------------------------------- String title; // Title used for the tab BoardsPane boardsPane; // Related boards pane ScrollView scrollView; // Component in the JTabbedPane //~ Constructors ------------------------------------------------------- public ViewTab (String title, BoardsPane boardsPane, ScrollView scrollView) { this.title = title; this.boardsPane = boardsPane; this.scrollView = scrollView; // Make the new view reuse the common zoom and rubber instances RubberPanel rubberPanel = scrollView.getView(); rubberPanel.setZoom(zoom); rubberPanel.setRubber(rubber); // Set the model size if (sheet.getPicture() != null) { rubberPanel.setModelSize(sheet.getDimension()); } // Force scroll bar computations zoom.fireStateChanged(); } //~ Methods ------------------------------------------------------------ //------------// // deselected // //------------// /** * Run when this tab gets deselected. */ public void deselected () { logger.debug("SheetAssembly: {} viewTab.deselected for {}", sheet.getId(), this); // Disconnection of events RubberPanel rubberPanel = scrollView.getView(); rubberPanel.unsubscribe(); // Disconnection of related boards, if any if ((boardsPane != null) && component.isVisible()) { boardsPane.disconnect(); } } //--------// // remove // //--------// /** * Remove this viewTab instance. */ public void remove () { RubberPanel rubberPanel = scrollView.getView(); rubberPanel.unsetZoom(zoom); rubberPanel.unsetRubber(rubber); JScrollPane scrollPane = scrollView.getComponent(); viewsPane.remove(scrollPane); tabs.remove(scrollPane); logger.debug("Removed tab: {}", this); } //----------// // selected // //----------// /** * Run when this tab gets selected. */ public void selected () { logger.debug("SheetAssembly: {} viewTabSelected for {} dim:{}", sheet.getId(), this, scrollView.getView().getPreferredSize()); // Link rubber with proper view RubberPanel rubberPanel = scrollView.getView(); rubber.connectComponent(rubberPanel); rubber.setMouseMonitor(rubberPanel); // Make connections to events rubberPanel.subscribe(); // Restore display of proper context logger.debug("{} showing:{}", this, component.isShowing()); if (component.isShowing()) { displayBoards(); } // Force update of LocationEvent LocationEvent locationEvent = (LocationEvent) locationService.getLastEvent( LocationEvent.class); Rectangle location = (locationEvent != null) ? locationEvent.getData() : null; if (location != null) { locationService.publish( new LocationEvent(this, locationEvent.hint, null, location)); } // Keep the same scroll bar positions as with previous tab if (previousTab != null) { JScrollPane prev = previousTab.scrollView.getComponent(); final int vert = prev.getVerticalScrollBar().getValue(); final int hori = prev.getHorizontalScrollBar().getValue(); JScrollPane scrollPane = scrollView.getComponent(); scrollPane.getVerticalScrollBar().setValue(vert); scrollPane.getHorizontalScrollBar().setValue(hori); } } //----------// // toString // //----------// @Override public String toString () { StringBuilder sb = new StringBuilder("{"); sb.append(getClass().getSimpleName()); sb.append(" ").append(title); sb.append("}"); return sb.toString(); } //--------------------// // disconnectKeyboard // //--------------------// private void disconnectKeyboard () { if (boardsPane != null) { for (Component topComp : boardsPane.getComponent(). getComponents()) { for (Component comp : ((Container) topComp).getComponents()) { if (comp instanceof JComponent) { ((JComponent) comp).resetKeyboardActions(); } } } } } //---------------// // displayBoards // //---------------// private void displayBoards () { if (boardsPane != null) { // (Re)connect the boards to their selection inputs if needed boardsPane.connect(); // Display the boards pane related to the selected view Main.getGui().setBoardsPane(boardsPane.getComponent()); } else { Main.getGui().setBoardsPane(null); } } } }