//------------------------------------------------------------------------------------------------// // // // B o o k A c t i o n s // // // //------------------------------------------------------------------------------------------------// // // // Copyright © NoteLite 2026. All rights reserved. // // This program is free software: you can redistribute it and/or modify it under the terms of the // GNU Affero General Public License as published by the Free Software Foundation, either version // 3 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License along with this // program. If not, see . //------------------------------------------------------------------------------------------------// // package com.notelite.omr.sheet.ui; import com.notelite.omr.Main; import com.notelite.omr.OMR; import com.notelite.omr.classifier.SampleRepository; import com.notelite.omr.classifier.ui.SampleBrowser; import com.notelite.omr.constant.Constant; import com.notelite.omr.constant.ConstantSet; import com.notelite.omr.log.LogUtil; import com.notelite.omr.plugin.Plugin; import com.notelite.omr.plugin.PluginsManager; import com.notelite.omr.practice.PianoBooster; import com.notelite.omr.practice.PracticeStudio; import com.notelite.omr.score.MidiAbstractions; import com.notelite.omr.score.MidiExporter; import com.notelite.omr.score.PageRef; import com.notelite.omr.score.Score; import com.notelite.omr.score.ScoreReduction; import com.notelite.omr.score.ui.BookParameters; import com.notelite.omr.score.ui.LogicalPartsEditor; import com.notelite.omr.score.ui.SheetScaling; import com.notelite.omr.sheet.Book; import com.notelite.omr.sheet.BookManager; import com.notelite.omr.sheet.ExportPattern; import com.notelite.omr.sheet.ScaleBuilder; import com.notelite.omr.sheet.Sheet; import com.notelite.omr.sheet.SheetStub; import com.notelite.omr.sheet.Staff; import com.notelite.omr.sheet.StaffManager; import com.notelite.omr.sheet.grid.StaffProjector; import com.notelite.omr.sheet.stem.StemScaler; import com.notelite.omr.sig.ui.InterController; import com.notelite.omr.step.OmrStep; import com.notelite.omr.step.StepPause; import com.notelite.omr.ui.BoardsPane; import com.notelite.omr.ui.OmrGui; import com.notelite.omr.ui.ViewParameters; import static com.notelite.omr.ui.action.AdvancedTopics.swapProcessedSheets; import com.notelite.omr.ui.util.OmrFileFilter; import com.notelite.omr.ui.util.UIUtil; import com.notelite.omr.ui.util.UserOpt; import com.notelite.omr.ui.util.WaitingTask; import com.notelite.omr.ui.util.WebBrowser; import com.notelite.omr.ui.view.HistoryMenu; import com.notelite.omr.ui.view.ScrollView; import com.notelite.omr.util.FileUtil; import com.notelite.omr.util.PathListTask; import com.notelite.omr.util.SheetPath; import com.notelite.omr.util.SheetPathHistory; import com.notelite.omr.util.VoidTask; import com.notelite.omr.util.WrappedBoolean; import com.notelite.omr.util.param.Param; import org.jdesktop.application.Action; import org.jdesktop.application.Application; import org.jdesktop.application.ApplicationAction; import org.jdesktop.application.ApplicationActionMap; import org.jdesktop.application.ResourceMap; import org.jdesktop.application.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.io.File; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import static java.text.MessageFormat.format; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.filechooser.FileFilter; /** * Class BookActions gathers all UI actions related to current book. *

* Swing EDT processes Runnable instances found in its event queue. * Via {@link SwingUtilities} new Runnable instances can further be appended to the event queue. * Unless log context is explicitly started and stopped in such Runnable, there is no reliable way * to set log context for the EDT. *

* By definition, all actions defined in this class are initiated on Swing EDT. * Hence, if log context handling is wanted, the action must delegate processing to a separate task * (log context can easily be handled in a non-EDT thread). * * @author NoteLite Contributors */ public class BookActions extends StubDependent { //~ Static fields/initializers ----------------------------------------------------------------- private static final Constants constants = new Constants(); private static final Logger logger = LoggerFactory.getLogger(BookActions.class); private static final ResourceMap resources = Application.getInstance().getContext() .getResourceMap(BookActions.class); private static final String doYouConfirm = "\n" + resources.getString("doYouConfirm"); /** Default parameter. */ public static final Param defaultPromptOnClosingUnsaved = new PromptOnClosingUnsaved(); //~ Instance fields ---------------------------------------------------------------------------- /** Sub-menu on images history. */ private final HistoryMenu imageHistoryMenu; /** Sub-menu on books history. */ private final HistoryMenu bookHistoryMenu; /** The action that toggles repetitive input mode. */ private final ApplicationAction repetitiveInputAction; /** The action that toggles sheet validity. */ private final ApplicationAction toggleValidityAction; //~ Constructors ------------------------------------------------------------------------------- /** * Creates a new BookActions object. */ private BookActions () { final BookManager mgr = BookManager.getInstance(); imageHistoryMenu = new HistoryMenu(mgr.getImageHistory(), LoadImageTask.class); bookHistoryMenu = new HistoryMenu(mgr.getBookHistory(), LoadBookTask.class); ApplicationActionMap actionMap = OmrGui.getApplication().getContext().getActionMap(this); repetitiveInputAction = (ApplicationAction) actionMap.get("toggleRepetitiveInput"); toggleValidityAction = (ApplicationAction) actionMap.get("toggleSheetValidity"); updateSheetValidity(null); } //~ Methods ------------------------------------------------------------------------------------ /** Practice the current score with live instrument input in the local practice studio. */ @Action(enabledProperty = BOOK_IDLE) public void openPracticeStudio (ActionEvent e) { PracticeStudio.open(StubsController.getCurrentBook()); } /** Export the selected movement and open the mature MIDI-keyboard practice application. */ @Action(enabledProperty = BOOK_IDLE) public void openPianoBooster (ActionEvent e) { PianoBooster.open(StubsController.getCurrentBook()); } @Action public void configurePianoBooster (ActionEvent e) { PianoBooster.configure(); } /** Import a reviewed MusicXML result from another recognizer without opening a book first. */ @Action public void importPracticeScore (ActionEvent e) { JFileChooser chooser = new JFileChooser(); chooser.setDialogTitle(resources.getString("importPracticeScore.Action.text")); chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); chooser.setFileFilter(new OmrFileFilter("MusicXML (*.xml, *.musicxml, *.mxl)", new String[] { ".xml", ".musicxml", ".mxl" })); if (chooser.showOpenDialog(OMR.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { PracticeStudio.openImported(chooser.getSelectedFile().toPath()); } } @Action public void importPianoBoosterMidi (ActionEvent e) { PianoBooster.importMidi(); } /** Explain the supported file interchange; external products remain independently licensed. */ @Action public void openExternalScoreTools (ActionEvent e) { Object[] choices = { resources.getString("externalScores.import"), resources.getString("externalScores.scanScoreHelp"), resources.getString("externalScores.close") }; int choice = JOptionPane.showOptionDialog(OMR.gui.getFrame(), resources.getString("externalScores.message"), resources.getString("openExternalScoreTools.Action.text"), JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, choices, choices[0]); if (choice == 0) { importPracticeScore(e); } else if (choice == 1) { WebBrowser.getBrowser().launch(URI.create( "https://scanscore.zendesk.com/hc/en-us/articles/16769352622610-Export")); } } //--------------// // annotateBook // //--------------// @Action(enabledProperty = BOOK_IDLE) public Task annotateBook (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if ((book == null) || !hasValidSelectedSheets(book)) { return null; } return new VoidTask() { @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(book); book.annotate(book.getValidSelectedStubs()); } catch (Throwable ex) { logger.warn("Error in annotateBook {}", ex.toString(), ex); } finally { LogUtil.stopBook(); } return null; } }; } //---------------// // annotateSheet // //---------------// @Action(enabledProperty = STUB_IDLE) public Task annotateSheet (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return null; } final Sheet sheet = stub.getSheet(); return new VoidTask() { @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(sheet.getStub()); sheet.annotate(); } catch (Throwable ex) { logger.warn("Error in annotateSheet {}", ex.toString(), ex); } finally { LogUtil.stopBook(); } return null; } }; } //-------------// // bookHistory // //-------------// @Action public void bookHistory (ActionEvent e) { logger.info("bookHistory"); } //------------// // browseBook // //------------// /** * Launch the tree display of the current book. * * @param e the event that triggered this action */ @Action(enabledProperty = STUB_AVAILABLE) public void browseBook (ActionEvent e) { OmrGui.getApplication().show(StubsController.getCurrentBook().getBrowserFrame()); } //-------------------// // browseBookSamples // //-------------------// /** * Action to browse the separate sample repository of the current book. * * @param e the event that triggered this action * @return the UI task to perform */ @Action(enabledProperty = STUB_AVAILABLE) public Task browseBookSamples (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if (book != null) { if (book.hasSpecificRepository()) { return new SampleBrowser.Waiter(resources.getString("launchingBookSampleBrowser")) { @Override protected SampleBrowser doInBackground () throws Exception { return SampleBrowser.getInstance(book); } }; } else { logger.info(format(resources.getString("noBookRepo.pattern"), book.getRadix())); } } return null; } //-----------------// // choosePrintPath // //-----------------// private Path choosePrintPath (Book book, String preExt) { final String ext = preExt + OMR.PRINT_EXTENSION; Path defaultBookPath = BookManager.getDefaultPrintPath(book); Path bookSansExt = FileUtil.avoidExtensions(defaultBookPath, OMR.PRINT_EXTENSION); if (!preExt.isEmpty()) { bookSansExt = FileUtil.avoidExtensions(bookSansExt, preExt); } defaultBookPath = Paths.get(bookSansExt + ext); return UIUtil.pathChooser( true, OMR.gui.getFrame(), defaultBookPath, filter(ext), resources.getString("chooseBookPrint")); } //-----------------// // choosePrintPath // //-----------------// private Path choosePrintPath (SheetStub stub, String preExt) { final Book book = stub.getBook(); final String ext = preExt + OMR.PRINT_EXTENSION; final Path defaultBookPath = BookManager.getDefaultPrintPath(book); final Path bookSansExt = FileUtil.avoidExtensions(defaultBookPath, OMR.PRINT_EXTENSION); final String sheetSuffix = book.isMultiSheet() ? (OMR.SHEET_SUFFIX + stub.getNumber()) : ""; final Path defaultSheetPath = Paths.get(bookSansExt + sheetSuffix + ext); return UIUtil.pathChooser( true, OMR.gui.getFrame(), defaultSheetPath, filter(ext), resources.getString("chooseSheetPrint")); } //-----------// // closeBook // //-----------// /** * Action that handles the closing of the current book. * * @param e the event that triggered this action * @return the task which will close the book */ @Action(enabledProperty = STUB_AVAILABLE) public Task closeBook (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub != null) { final Book book = stub.getBook(); if (checkStored(book)) { // Pre-select the suitable "next" book tab, if any. StubsController.getInstance().selectOtherBook(book); // Now close the book (+ related tab) return new CloseBookTask(stub); } } return null; } //------------------// // defineParameters // //------------------// /** * Launch the dialog to set up book parameters. * * @param e the event that triggered this action */ @Action public void defineParameters (ActionEvent e) { applyUserSettings(StubsController.getCurrentStub()); } //--------------------// // defineSheetScaling // //--------------------// /** * Launch the dialog to check and modify sheet scaling data. * * @param e the event that triggered this action */ @Action(enabledProperty = STUB_AVAILABLE) public void defineSheetScaling (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); try { final WrappedBoolean apply = new WrappedBoolean(false); final SheetScaling sheetScaling = new SheetScaling(stub.getSheet()); final JOptionPane optionPane = new JOptionPane( sheetScaling.getComponent(), JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION); final String frameTitle = sheetScaling.getTitle(); final JDialog dialog = new JDialog(OMR.gui.getFrame(), frameTitle, true); // Modal flag dialog.setContentPane(optionPane); dialog.setName("SheetScalingDialog"); // For SAF life cycle optionPane.addPropertyChangeListener( (PropertyChangeEvent e1) -> { String prop = e1.getPropertyName(); if (dialog.isVisible() && (e1.getSource() == optionPane) && (prop.equals( JOptionPane.VALUE_PROPERTY))) { Object obj = optionPane.getValue(); int value = (Integer) obj; apply.set(value == JOptionPane.OK_OPTION); // Exit only if user gives up or enters correct data if (!apply.isSet() || sheetScaling.commit()) { dialog.setVisible(false); dialog.dispose(); } else { // Incorrect data, so don't exit yet try { // TODO: Is there a more civilized way? optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE); } catch (Exception ignored) {} } } }); dialog.pack(); OmrGui.getApplication().show(dialog); } catch (Throwable ex) { logger.warn("Error in defineSheetScaling {}", ex.toString(), ex); } } //---------------// // displayBinary // //---------------// /** * Action that allows to display the view on binary image * * @param e the event that triggered this action */ @Action(enabledProperty = BINARY_AVAILABLE) public void displayBinary (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); try { if (stub.isDone(OmrStep.BINARY)) { final SheetAssembly assembly = stub.getAssembly(); final SheetTab tab = SheetTab.BINARY_TAB; if (assembly.getView(tab.label) == null) { stub.getSheet().createBinaryView(); } else { assembly.selectViewTab(tab); } } else { logger.info(resources.getString("noBinaryImage")); } } catch (Throwable ex) { logger.warn("Error in displayBinary {}", ex.toString(), ex); } } //-------------// // displayData // //-------------// /** * Action that allows to display the view on data. * * @param e the event that triggered this action */ @Action(enabledProperty = GRID_AVAILABLE) public void displayData (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); try { if (stub.isDone(OmrStep.GRID)) { final SheetAssembly assembly = stub.getAssembly(); final SheetTab tab = SheetTab.DATA_TAB; if (assembly.getView(tab.label) == null) { stub.getSheet().displayDataTab(); } else { assembly.selectViewTab(tab); } } else { logger.info(resources.getString("noDataTab")); } } catch (Throwable ex) { logger.warn("Error in displayData {}", ex.toString(), ex); } } //-------------// // displayGray // //-------------// /** * Action that allows to display the view on initial gray image. * * @param e the event that triggered this action */ @Action(enabledProperty = GRAY_AVAILABLE) public void displayGray (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); final SheetAssembly assembly = stub.getAssembly(); final SheetTab tab = SheetTab.GRAY_TAB; if (assembly.getView(tab.label) == null) { stub.getSheet().createGrayView(); } else { assembly.selectViewTab(tab); } } //----------------// // displayNoStaff // //----------------// /** * Action that allows to display the view on no-staff buffer * * @param e the event that triggered this action */ @Action(enabledProperty = GRID_AVAILABLE) public void displayNoStaff (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub.isDone(OmrStep.GRID)) { final SheetAssembly assembly = stub.getAssembly(); final SheetTab tab = SheetTab.NO_STAFF_TAB; if (assembly.getView(tab.label) == null) { Sheet sheet = stub.getSheet(); // This may load the sheet... assembly.addViewTab( tab, new ScrollImageView(sheet, new NoStaffView(sheet)), new BoardsPane(new PixelBoard(sheet))); } else { assembly.selectViewTab(tab); } } else { logger.info(resources.getString("noStaffLines")); } } //------------------------// // displayStaffLineGlyphs // //------------------------// /** * Action that allows to display the view on staff underlying glyphs. * * @param e the event that triggered this action */ @Action(enabledProperty = GRID_AVAILABLE) public void displayStaffLineGlyphs (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub.isDone(OmrStep.GRID)) { final SheetAssembly assembly = stub.getAssembly(); final SheetTab tab = SheetTab.STAFF_LINE_TAB; if (assembly.getView(tab.label) == null) { Sheet sheet = stub.getSheet(); // This may load the sheet... assembly.addViewTab( tab, new ScrollImageView( sheet, new ImageView(sheet.getPicture().buildStaffLineGlyphsImage())), new BoardsPane(new PixelBoard(sheet))); } else { assembly.selectViewTab(tab); } } else { logger.info(resources.getString("noStaffLines")); } } //----------// // dumpBook // //----------// /** * Dump the internals of a book to system output. * * @param e the event that triggered this action */ @Action(enabledProperty = STUB_AVAILABLE) public void dumpBook (ActionEvent e) { logger.error("dumpBook() is not yet implemented."); ///BookController.getCurrentBook().dump(); } //-------------------// // dumpEventServices // //-------------------// /** * Action to dump the content of all event services * * @param e the event which triggered this action */ @Action(enabledProperty = STUB_AVAILABLE) public void dumpEventServices (ActionEvent e) { StubsController.getInstance().dumpCurrentSheetServices(); } //------------// // exportBook // //------------// /** * Export the current book using MusicXML format * * @param e the event that triggered this action * @return the task to launch in background */ @Action(enabledProperty = BOOK_IDLE) public Task exportBook (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if ((book == null) || !hasValidSelectedSheets(book)) { return null; } final Path exportPathSansExt = BookManager.getDefaultExportPathSansExt(book); if (exportPathSansExt != null) { //TODO: check/prompt for overwrite??? (perhaps several files) return new ExportBookTask(book, exportPathSansExt); } else { return exportBookAs(e); } } //--------------// // exportBookAs // //--------------// /** * Export the current book, in either MusicXML or MIDI format, to a * user-chosen location. The format is determined by the file type * dropdown (and the resulting extension) in the save dialog. * * @param e the event that triggered this action * @return the task to launch in background */ @Action(enabledProperty = BOOK_IDLE) public Task exportBookAs (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if ((book == null) || !hasValidSelectedSheets(book)) { return null; } final String ext = BookManager.getExportExtension(); final Path sansExt = BookManager.getDefaultExportPathSansExt(book); final Path defaultPath = Paths.get(sansExt + ext); final ExportTarget target = chooseExportTarget( defaultPath, resources.getString("chooseBookExport")); if ((target == null) || !isTargetConfirmed(target.path)) { return null; } if (target.format == ExportFormat.MIDI) { final Path bookPathSansMidiExt = stripExtension( target.path, MidiAbstractions.MIDI_EXTENSION); return new ExportBookMidiTask(book, bookPathSansMidiExt); } // Remove extensions if any (.opus.mxl, .mxl, .xml, .mvt#.mxl, .mvt#.xml) final Path bookPathSansExt = ExportPattern.getPathSansExt(target.path); return new ExportBookTask(book, bookPathSansExt); } //---------------// // exportSheetAs // //---------------// /** * Export the currently selected sheet in either MusicXML or MIDI format, * to a user-chosen location. The format is picked from the file type * dropdown in the save dialog. * * @param e the event that triggered this action * @return the task to launch in background */ @Action(enabledProperty = STUB_IDLE) public Task exportSheetAs (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return null; // Not likely to happen, but safer } final Book book = stub.getBook(); final Path bookSansExt = BookManager.getDefaultExportPathSansExt(book); final boolean compressed = BookManager.useCompression(); final String ext = compressed ? OMR.COMPRESSED_SCORE_EXTENSION : OMR.SCORE_EXTENSION; final String suffix = book.isMultiSheet() ? (OMR.SHEET_SUFFIX + stub.getNumber()) : ""; final Path defaultSheetPath = Paths.get(bookSansExt + suffix + ext); final ExportTarget target = chooseExportTarget( defaultSheetPath, resources.getString("chooseSheetExport")); if ((target == null) || !isTargetConfirmed(target.path)) { return null; } if (target.format == ExportFormat.MIDI) { return new ExportSheetMidiTask(stub.getSheet(), target.path); } return new ExportSheetTask(stub.getSheet(), target.path); } //--------------------// // chooseExportTarget // //--------------------// /** * Prompt the user with a save dialog that offers a file-type dropdown for * MusicXML and MIDI. Switching the dropdown live-rewrites the extension on * the entered file name. The format is finally determined by the picked * extension. * * @param defaultPath pre-filled path including default extension * @param dialogTitle localized title * @return chosen target with format, or null when the user cancels */ private static ExportTarget chooseExportTarget (Path defaultPath, String dialogTitle) { final String midiExt = MidiAbstractions.MIDI_EXTENSION; final String mxlExt = OMR.COMPRESSED_SCORE_EXTENSION; final String xmlExt = OMR.SCORE_EXTENSION; final FileFilter mxlFilter = new OmrFileFilter( resources.getString("filterMusicXml.description"), new String[] { mxlExt, xmlExt }); final FileFilter midiFilter = new OmrFileFilter( resources.getString("filterMidi.description"), new String[] { midiExt }); final JFileChooser fc = new JFileChooser(); fc.setDialogTitle(dialogTitle); fc.setAcceptAllFileFilterUsed(false); fc.addChoosableFileFilter(mxlFilter); fc.addChoosableFileFilter(midiFilter); final File defaultFile = defaultPath.toFile(); if (defaultFile.getParentFile() != null) { fc.setCurrentDirectory(defaultFile.getParentFile()); } fc.setSelectedFile(defaultFile); final String defaultName = defaultFile.getName().toLowerCase(); fc.setFileFilter(defaultName.endsWith(midiExt) ? midiFilter : mxlFilter); // Live-rewrite the typed file name when the user switches file type. fc.addPropertyChangeListener(JFileChooser.FILE_FILTER_CHANGED_PROPERTY, evt -> { final File current = fc.getSelectedFile(); if (current == null) { return; } final FileFilter newFilter = (FileFilter) evt.getNewValue(); final String newExt = (newFilter == midiFilter) ? midiExt : (BookManager.useCompression() ? mxlExt : xmlExt); final String name = current.getName(); final int dot = name.lastIndexOf('.'); final String stem = (dot > 0) ? name.substring(0, dot) : name; fc.setSelectedFile(new File(current.getParentFile(), stem + newExt)); }); final int result = fc.showSaveDialog(OMR.gui.getFrame()); if (result != JFileChooser.APPROVE_OPTION) { return null; } final File selected = fc.getSelectedFile(); if (selected == null) { return null; } final String selectedName = selected.getName().toLowerCase(); Path resolved = selected.toPath(); ExportFormat format; if (selectedName.endsWith(midiExt)) { format = ExportFormat.MIDI; } else if (selectedName.endsWith(mxlExt) || selectedName.endsWith(xmlExt)) { format = ExportFormat.MUSIC_XML; } else { // No (or unknown) extension: trust the active filter and append. final FileFilter active = fc.getFileFilter(); if (active == midiFilter) { format = ExportFormat.MIDI; resolved = resolved.resolveSibling(selected.getName() + midiExt); } else { format = ExportFormat.MUSIC_XML; final String fallback = BookManager.useCompression() ? mxlExt : xmlExt; resolved = resolved.resolveSibling(selected.getName() + fallback); } } return new ExportTarget(resolved, format); } //----------------// // stripExtension // //----------------// private static Path stripExtension (Path path, String ext) { final String name = path.getFileName().toString(); if (name.toLowerCase().endsWith(ext)) { return path.resolveSibling(name.substring(0, name.length() - ext.length())); } return path; } //--------------------// // getBookHistoryMenu // //--------------------// public HistoryMenu getBookHistoryMenu () { return bookHistoryMenu; } //---------------------// // getImageHistoryMenu // //---------------------// public HistoryMenu getImageHistoryMenu () { return imageHistoryMenu; } //------------------------// // hasValidSelectedSheets // //------------------------// private boolean hasValidSelectedSheets (Book book) { final List stubs = book.getValidSelectedStubs(); if (stubs.isEmpty()) { logger.warn("No valid selected sheets in {}", book); return false; } else { return true; } } //---------------------// // invokeDefaultPlugin // //---------------------// /** * Action to invoke the default score external editor * * @param e the event that triggered this action * @return the task to launch in background */ @Action(enabledProperty = STUB_IDLE) public Task invokeDefaultPlugin (ActionEvent e) { Plugin defaultPlugin = PluginsManager.getInstance().getDefaultPlugin(); if (defaultPlugin == null) { logger.warn("No default plugin defined"); return null; } // Current score export file final Book book = StubsController.getCurrentBook(); if (book == null) { return null; } else { return defaultPlugin.getTask(book); } } //-----------// // openBooks // //-----------// /** * Action that let the user select one or several books. * * @param e the event that triggered this action * @return the asynchronous task, or null */ @Action public LoadFilesTask openBooks (ActionEvent e) { final Path[] paths = selectBookPaths(false, Paths.get(BookManager.getDefaultBookFolder())); if (paths.length > 0) { return new LoadFilesTask(Arrays.asList(paths)); } return null; } //----------------// // openImageFiles // //----------------// /** * Action that let the user select one or several image files interactively. * * @param e the event that triggered this action * @return the asynchronous task, or null */ @Action public LoadFilesTask openImageFiles (ActionEvent e) { final Path[] paths = selectImagePaths(); if (paths.length > 0) { return new LoadFilesTask(Arrays.asList(paths)); } return null; } //----------------// // openRecentBook // //----------------// /** * Action that let the user open the book most recently closed. * * @param e the event that triggered this action * @return the asynchronous task, or null */ @Action public LoadBookTask openRecentBook (ActionEvent e) { final SheetPathHistory bookHistory = BookManager.getInstance().getBookHistory(); while (!bookHistory.isEmpty()) { final SheetPath sheetPath = bookHistory.getFirst(); if (sheetPath != null) { final Path path = sheetPath.getBookPath(); if (Files.exists(path)) { return new LoadBookTask(sheetPath); } else { logger.warn(format(resources.getString("fileNotFound.pattern"), path)); bookHistory.remove(sheetPath); } } } return null; } //-----------// // plotScale // //-----------// /** * Action that allows to display the plot of Scale Builder. * * @param e the event that triggered this action */ @Action(enabledProperty = STUB_AVAILABLE) public void plotScale (ActionEvent e) { SheetStub stub = StubsController.getCurrentStub(); if (stub != null) { if (stub.isDone(OmrStep.BINARY)) { new ScaleBuilder(stub.getSheet()).displayChart(); } else { logger.info(resources.getString("noScaleData")); } } } //------------// // plotStaves // //------------// /** * Action that allows to display the horizontal projection of a selected staff. *

* We need a sub-menu to select proper staff. * * @param e the event that triggered this action */ @Action(enabledProperty = STUB_AVAILABLE) public void plotStaves (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return; } final Sheet sheet = stub.getSheet(); final StaffManager staffManager = sheet.getStaffManager(); if (staffManager.getStaffCount() == 0) { logger.info(resources.getString("noStaffData")); return; } final JPopupMenu popup = new JPopupMenu("Staves IDs"); // Menu title final JMenuItem title = new JMenuItem("Select staff ID:"); title.setHorizontalAlignment(SwingConstants.CENTER); title.setEnabled(false); popup.add(title); popup.addSeparator(); final ActionListener listener = (ActionEvent e1) -> { final int index = Integer.decode(e1.getActionCommand()) - 1; final Staff staff = staffManager.getStaff(index); new StaffProjector(sheet, staff, null).plot(); }; // Populate popup for (Staff staff : staffManager.getStaves()) { final JMenuItem item = new JMenuItem("" + staff.getId()); item.addActionListener(listener); popup.add(item); } // Display popup menu final JFrame frame = OMR.gui.getFrame(); popup.show(frame, frame.getWidth() / 6, frame.getHeight() / 4); } //----------// // plotStem // //----------// /** * Action that allows to display the plot of stem scaler. * * @param e the event that triggered this action */ @Action(enabledProperty = STUB_AVAILABLE) public void plotStem (ActionEvent e) { SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return; } if (stub.isDone(OmrStep.STEM_SEEDS)) { new StemScaler(stub.getSheet()).displayChart(); } else { logger.info(resources.getString("noStemData")); } } //-----------// // printBook // //-----------// /** * Print the current book, as a PDF file * * @param e the event that triggered this action * @return the task to launch in background */ @Action(enabledProperty = BOOK_IDLE) public Task printBook (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if ((book == null) || !hasValidSelectedSheets(book)) { return null; } final Path bookPrintPath = BookManager.getDefaultPrintPath(book); if ((bookPrintPath != null) && isTargetConfirmed(bookPrintPath)) { return new PrintBookTask(book, bookPrintPath); } return printBookAs(e); } //-------------// // printBookAs // //-------------// /** * Write the current book, using PDF format, to a user-provided file. * * @param e the event that triggered this action * @return the task to launch in background */ @Action(enabledProperty = BOOK_IDLE) public Task printBookAs (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if ((book == null) || !hasValidSelectedSheets(book)) { return null; } // Select target book print path final Path bookPrintPath = choosePrintPath(book, ""); if ((bookPrintPath == null) || !isTargetConfirmed(bookPrintPath)) { return null; } return new PrintBookTask(book, bookPrintPath); } //--------------// // printSheetAs // //--------------// /** * Write the currently selected sheet, using PDF format, to a user-provided location. * * @param e the event that triggered this action * @return the task to launch in background */ @Action(enabledProperty = STUB_IDLE) public Task printSheetAs (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return null; } // Let the user select a PDF output file final Path sheetPrintPath = choosePrintPath(stub, ""); if ((sheetPrintPath == null) || !isTargetConfirmed(sheetPrintPath)) { return null; } return new PrintSheetTask(stub.getSheet(), sheetPrintPath); } //-----------------// // printSheetWatch // //-----------------// /** * Print out the watch on sheet processing. * * @param e the event that triggered this action */ @Action(enabledProperty = STUB_IDLE) public void printSheetWatch (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub != null) { stub.printWatch(true); } } //---------------// // rebuildScores // //---------------// /** * Action to rebuild book score(s) from scratch. */ @Action(enabledProperty = BOOK_IDLE) public void rebuildScores () { final Book book = StubsController.getCurrentBook(); if (book == null) { return; } final String msg = format(resources.getString("rebuildScores.pattern"), book.getRadix()); if (!OMR.gui.displayConfirmation(msg + doYouConfirm)) { return; } book.clearScores(); book.updateScores(null); } //------// // redo // //------// /** * Action to redo undone user modification. * * @param e the event that triggered this action */ @Action(enabledProperty = REDOABLE) public void redo (ActionEvent e) { SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return; } Sheet sheet = stub.getSheet(); InterController controller = sheet.getInterController(); controller.redo(); } //-----------// // resetBook // //-----------// private Task resetBook (OmrStep step) { final Book book = StubsController.getCurrentBook(); if (book == null) { return null; } final String msg = format( resources.getString("resetBookToEnd.pattern"), book.getRadix(), step); if (!OMR.gui.displayConfirmation(msg + doYouConfirm)) { return null; } return new ResetBookTask(book, step); } //-------------------// // resetBookToBinary // //-------------------// /** * Action that resets to BINARY all (valid selected) sheets of current book. * * @param e the event that triggered this action * @return the background task */ @Action(enabledProperty = BOOK_IDLE) public Task resetBookToBinary (ActionEvent e) { return resetBook(OmrStep.BINARY); } //-----------------// // resetBookToGray // //-----------------// /** * Action that resets all (valid selected) sheets of current book to gray. * * @param e the event that triggered this action * @return the background task */ @Action(enabledProperty = BOOK_IDLE) public Task resetBookToGray (ActionEvent e) { // Check book input path still exists final Book book = StubsController.getCurrentBook(); if (book != null) { final Path inputPath = book.getInputPath(); // Null for a compound book if ((inputPath != null) && !Files.exists(inputPath)) { OMR.gui.displayWarning("Cannot find " + inputPath, "Source images not available"); return null; } } return resetBook(OmrStep.LOAD); } //------------// // sampleBook // //------------// @Action(enabledProperty = BOOK_IDLE) public Task sampleBook (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if ((book == null) || !hasValidSelectedSheets(book)) { return null; } return new SampleBookTask(book); } //-------------// // sampleSheet // //-------------// @Action(enabledProperty = STUB_IDLE) public Task sampleSheet (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return null; } return new SampleSheetTask(stub.getSheet()); } //----------// // saveBook // //----------// /** * Action to save the internals of the current book. * * @param e the event that triggered this action * @return the UI task to perform */ @Action(enabledProperty = BOOK_MODIFIED_OR_UPGRADED) public Task saveBook (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if (book == null) { return null; } try { final Path bookPath = BookManager.getDefaultSavePath(book); if ((book.getBookPath() != null) && (bookPath.toAbsolutePath().equals( book.getBookPath().toAbsolutePath()) || isTargetConfirmed(bookPath))) { return new StoreBookTask(book, bookPath); } return saveBookAs(e); } catch (Throwable ex) { logger.warn("Error in saveBook {}", ex.toString(), ex); } return null; } //------------// // saveBookAs // //------------// @Action(enabledProperty = STUB_AVAILABLE) public Task saveBookAs (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if (book == null) { return null; } try { // Let the user select a book output file final Path defaultBookPath = BookManager.getDefaultSavePath(book); final Path targetPath = selectBookPath(true, defaultBookPath); final Path ownPath = book.getBookPath(); if ((targetPath != null) && (((ownPath != null) && ownPath.toAbsolutePath().equals( targetPath.toAbsolutePath())) || isTargetConfirmed(targetPath))) { return new StoreBookTask(book, targetPath); } } catch (Throwable ex) { logger.warn("Error in saveBookAs {}", ex.toString(), ex); } return null; } //--------------------// // saveBookRepository // //--------------------// /** * Action to save the separate repository of the current book. * * @param e the event that triggered this action * @return the UI task to perform */ @Action(enabledProperty = BOOK_MODIFIED_OR_UPGRADED) public Task saveBookRepository (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if (book == null) { return null; } try { if (book.hasAllocatedRepository()) { SampleRepository repo = book.getSampleRepository(); if (repo.isModified()) { repo.storeRepository(); } } } catch (Throwable ex) { logger.warn("Error in saveBookRepository {}", ex.toString(), ex); } return null; } //--------------// // selectSheets // //--------------// /** * Specify sheets selection. * * @param e the event that triggered this action */ @Action(enabledProperty = BOOK_IDLE) public void selectSheets (ActionEvent e) { Book book = StubsController.getCurrentBook(); if (book == null) { return; } try { if (new StubsSelection(book).getSheetsSpec()) { book.clearScores(); } } catch (Throwable ex) { logger.warn("Error in selectSheets {}", ex.toString(), ex); } } //---------------// // splitAndMerge // //---------------// /** * Action that let the user build a new book from various pieces. * * @param e the event that triggered this action */ @Action public void splitAndMerge (ActionEvent e) { OmrGui.getApplication().show(new SplitAndMerge(null).getComponent()); } //---------------// // splitAndMerge // //---------------// /** * Action that let the user build a new book from various pieces. * * @param playListPath path to the provided playList to start with */ public void splitAndMerge (Path playListPath) { OmrGui.getApplication().show(new SplitAndMerge(playListPath).getComponent()); } //-------------------// // stopTranscription // //-------------------// /** * Stop the book transcription ASAP. * * @param e the event that triggered this action */ @Action(enabledProperty = BOOK_PAUSABLE) public void stopTranscription (ActionEvent e) { final Book book = StubsController.getCurrentBook(); if (book == null) { return; } book.setPauseRequired(true); logger.info("Pause required for book {} ...", book.getRadix()); } //------------// // swapSheets // //------------// /** * Swap out (most of) book sheets. * * @param e the event that triggered this action */ @Action(enabledProperty = BOOK_IDLE) public void swapSheets (ActionEvent e) { Book book = StubsController.getCurrentBook(); if (book == null) { return; } try { book.swapAllSheets(); } catch (Throwable ex) { logger.warn("Error in swapSheets {}", ex.toString(), ex); } } //-----------------------// // toggleRepetitiveInput // //-----------------------// /** * Toggle repetitive input mode. *

* When this mode is active, focus is on manual insertion of items, with as few user actions as * possible. * * @param e the event that triggered this action */ @Action(enabledProperty = REPETITIVE_INPUT_SELECTABLE) public void toggleRepetitiveInput (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return; } final SheetEditor sheetEditor = stub.getSheet().getSheetEditor(); sheetEditor.toggleRepetitiveInputMode(); repetitiveInputAction.setSelected(sheetEditor.isRepetitiveInputMode()); } //---------------------// // toggleSheetValidity // //---------------------// /** * Action that toggles the validity of currently selected sheet. * * @param e the event that triggered this action */ @Action(enabledProperty = STUB_IDLE) public void toggleSheetValidity (ActionEvent e) { final StubsController controller = StubsController.getInstance(); final SheetStub stub = controller.getSelectedStub(); if (stub != null) { final boolean isValid = stub.isValid(); final String pattern = resources.getString( isValid ? "setSheetInvalid.pattern" : "setSheetValid.pattern"); final String msg = format(pattern, stub.getId()); if (!OMR.gui.displayConfirmation(msg + doYouConfirm)) { return; } if (isValid) { stub.invalidate(); if (ViewParameters.getInstance().isInvalidSheetDisplay() == false) { controller.removeAssembly(stub); } } else { stub.validate(); } } } //----------------// // transcribeBook // //----------------// /** * Launch or complete the transcription of all sheets and merge them at book level. * * @param e the event that triggered this action * @return the task to launch in background */ @Action(enabledProperty = BOOK_TRANSCRIBABLE) public Task transcribeBook (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return null; } boolean swap = false; if (swapProcessedSheets() || Main.getCli().isSwap()) { // Make sure we will be able to save book incrementally final Book book = stub.getBook(); final Path defPath = BookManager.getDefaultSavePath(book); final Path bookPath = book.getBookPath(); if (((bookPath != null) && (defPath.toAbsolutePath().equals(bookPath.toAbsolutePath())) || isTargetConfirmed(defPath))) { swap = true; logger.info("Processed sheets will be swapped out"); } else { logger.info("No swap out for processed sheets"); } } return new TranscribeBookTask(stub, swap); } //-----------------// // transcribeSheet // //-----------------// /** * Launch or complete sheet transcription. * * @param e the event that triggered this action * @return the task to launch in background */ @Action(enabledProperty = STUB_TRANSCRIBABLE) public Task transcribeSheet (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return null; } return new TranscribeSheetTask(stub.getSheet()); } //------// // undo // //------// /** * Action to undo last user modification. * * @param e the event that triggered this action */ @Action(enabledProperty = UNDOABLE) public void undo (ActionEvent e) { SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return; } Sheet sheet = stub.getSheet(); InterController controller = sheet.getInterController(); controller.undo(); } //-----------------------// // updateRepetitiveInput // //-----------------------// /** * Update checkbox of repetitiveInput mode according to the current sheet * * @param sheet the current sheet */ public void updateRepetitiveInput (Sheet sheet) { final SheetStub stub = StubsController.getCurrentStub(); if (stub == sheet.getStub()) { repetitiveInputAction.setSelected(sheet.getSheetEditor().isRepetitiveInputMode()); } } //---------------------// // updateSheetValidity // //---------------------// /** * Update menu item according to sheet validity status. * * @param stub the sheet stub to process */ public final void updateSheetValidity (SheetStub stub) { if (stub == null) { toggleValidityAction.putValue( javax.swing.Action.SMALL_ICON, resources.getImageIcon("toggleSheetValidity.Action.smallIcon.true")); toggleValidityAction.putValue( javax.swing.Action.NAME, resources.getString("toggleSheetValidity.Action.text.none")); toggleValidityAction.putValue( javax.swing.Action.SHORT_DESCRIPTION, resources.getString("toggleSheetValidity.Action.shortDescription.none")); } else if (stub == StubsController.getCurrentStub()) { final boolean isValid = stub.isValid(); toggleValidityAction.putValue( javax.swing.Action.SMALL_ICON, resources.getImageIcon("toggleSheetValidity.Action.smallIcon." + isValid)); toggleValidityAction.putValue( javax.swing.Action.NAME, resources.getString("toggleSheetValidity.Action.text." + isValid)); toggleValidityAction.putValue( javax.swing.Action.SHORT_DESCRIPTION, resources.getString("toggleSheetValidity.Action.shortDescription." + isValid)); } } //-------------// // upgradeBook // //-------------// /** * Complete the upgrade of all sheets. * * @param e the event that triggered this action * @return the task to launch in background */ @Action(enabledProperty = BOOK_UPGRADABLE) public Task upgradeBook (ActionEvent e) { final SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return null; } return new UpgradeBookTask(stub.getBook()); } //------------// // zoomHeight // //------------// /** * Action that allows to adjust the display zoom, so that the full height is shown. * * @param e the event that triggered this action */ @Action(enabledProperty = STUB_AVAILABLE) public void zoomHeight (ActionEvent e) { SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return; } SheetAssembly assembly = stub.getAssembly(); if (assembly == null) { return; } ScrollView scrollView = assembly.getSelectedScrollView(); if (scrollView == null) { return; } scrollView.fitHeight(); } //-----------// // zoomWidth // //-----------// /** * Action that allows to adjust the display zoom, so that the full width is shown. * * @param e the event that triggered this action */ @Action(enabledProperty = STUB_AVAILABLE) public void zoomWidth (ActionEvent e) { SheetStub stub = StubsController.getCurrentStub(); if (stub == null) { return; } SheetAssembly assembly = stub.getAssembly(); if (assembly == null) { return; } ScrollView scrollView = assembly.getSelectedScrollView(); if (scrollView == null) { return; } scrollView.fitWidth(); } //~ Static Methods ----------------------------------------------------------------------------- //-------------------// // applyUserSettings // //-------------------// /** * Prompt the user for interactive confirmation or modification of book/sheet * parameters. * * @param stub the current sheet stub, or null */ public static void applyUserSettings (final SheetStub stub) { final Book book = (stub != null) ? stub.getBook() : null; if (book != null) { // Dialog already active? final JDialog activeDialog = book.getParameterDialog(); if (activeDialog != null) { activeDialog.setVisible(true); return; } } // Create a brand new dialog try { final BookParameters bookParameters = new BookParameters(stub); final String frameTitle = bookParameters.getTitle(); final JDialog dialog = new JDialog(OMR.gui.getFrame(), frameTitle, false); // Non modal // For SAF life cycle (to save dialog size and location across application runs) dialog.setName("ScoreParamsDialog"); // To avoid memory leak when user closes window via the upper right cross dialog.addWindowListener(new WindowAdapter() { @Override public void windowClosing (WindowEvent we) { if (book != null) { book.setParameterDialog(null); } dialog.dispose(); } }); if (book != null) { book.setParameterDialog(dialog); } // User actions on buttons OK, Apply, Cancel final JOptionPane optionPane = new JOptionPane( bookParameters.getComponent(), JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[] { UserOpt.OK, UserOpt.Apply, UserOpt.Cancel }); optionPane.addPropertyChangeListener(e -> { if (dialog.isVisible() && (e.getSource() == optionPane) && (e.getPropertyName() .equals(JOptionPane.VALUE_PROPERTY))) { final Object choice = optionPane.getValue(); final boolean exit; if (choice == UserOpt.Cancel) { exit = true; } else if (choice == UserOpt.Apply) { bookParameters.commit(book); exit = false; } else if (choice == UserOpt.OK) { exit = bookParameters.commit(book); } else { exit = false; } if (exit) { if (book != null) { book.setParameterDialog(null); } dialog.setVisible(false); dialog.dispose(); } else { optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE); } } }); dialog.setContentPane(optionPane); dialog.pack(); OmrGui.getApplication().show(dialog); } catch (Exception ex) { logger.warn("Error in BookParameters {}", ex.toString(), ex); } } //-----------------// // checkParameters // //-----------------// /** * Make sure that the book parameters are properly set up, even by * prompting the user for them, otherwise return false * * @param book the provided book * @return true if OK, false otherwise */ public static boolean checkParameters (Book book) { // if (constants.promptParameters.getValue()) { // return applyUserSettings(sheet); // } else { // return true; // ///return fillParametersWithDefaults(sheet.getBook()); // } return true; } //-----------------// // checkParameters // //-----------------// /** * Make sure that the book parameters are properly set up, even by * prompting the user for them, otherwise return false * * @param sheet the provided sheet * @return true if OK, false otherwise */ public static boolean checkParameters (Sheet sheet) { // if (constants.promptParameters.getValue()) { // return applyUserSettings(sheet); // } else { // return true; // ///return fillParametersWithDefaults(sheet.getBook()); // } return true; } //-------------// // checkStored // //-------------// /** * Check whether the provided book has been saved if needed * (and therefore, if it can be closed) * * @param book the book to check * @return true if close is allowed, false if not */ public static boolean checkStored (Book book) { // Check the book scores for (Score score : book.getScores()) { final LogicalPartsEditor editor = score.getLogicalsEditor(); if (editor != null && !editor.canClose()) { return false; } } final String bookStatus = book.isModified() ? resources.getString("modified") : (book.isUpgraded() ? resources.getString("upgraded") : null); if ((bookStatus != null) && defaultPromptOnClosingUnsaved.getValue()) { final int answer = JOptionPane.showConfirmDialog( OMR.gui.getFrame(), format(resources.getString("saveBook.pattern"), bookStatus, book.getRadix())); if (answer == JOptionPane.YES_OPTION) { Path bookPath; if (book.getBookPath() == null) { // Find a suitable target file bookPath = BookManager.getDefaultSavePath(book); // Check the target is fine if (!isTargetConfirmed(bookPath)) { // Let the user select an alternate output file bookPath = selectBookPath(true, BookManager.getDefaultSavePath(book)); if ((bookPath == null) || !isTargetConfirmed(bookPath)) { return false; // No suitable target found } } } else { bookPath = book.getBookPath(); } try { // Save the book to target file book.store(bookPath, false); return true; // Book successfully saved } catch (Exception ex) { logger.warn("Error saving book", ex); return false; // Saving failed } } // Check whether user specifically chose NOT to save the book return answer == JOptionPane.NO_OPTION; } else { return true; } } //--------// // filter // //--------// public static OmrFileFilter filter (String ext) { return new OmrFileFilter(ext, new String[] { ext }); } //-------------// // getInstance // //-------------// /** * Report the single instance of BookActions in the application. * * @return the instance */ public static BookActions getInstance () { return LazySingleton.INSTANCE; } //-------------------// // isTargetConfirmed // //-------------------// /** * Check whether we have user confirmation to overwrite the target path. *

* This is a no-op if target does not already exist. * * @param target the path to be checked * @return false if explicitly not isTargetConfirmed, true otherwise */ public static boolean isTargetConfirmed (Path target) { return (!Files.exists(target)) || OMR.gui.displayConfirmation( format(resources.getString("overwriteFile.pattern"), target)); } //-----------------------// // preOpenBookParameters // //-----------------------// /** * Check whether we should pre-open book parameters dialog at any book creation. * * @return true if so */ public static boolean preOpenBookParameters () { return constants.preOpenBookParameters.isSet(); } //------------------------// // selectBookOrImagePaths // //------------------------// /** * Let the user interactively select book/image paths for reading. * * @param startPath starting path * @return the array of selected paths, perhaps empty but not null */ public static Path[] selectBookOrImagePaths (Path startPath) { final String suffixes = constants.validImageExtensions.getValue(); final String allSuffixes = OMR.BOOK_EXTENSION + " " + suffixes + " " + suffixes .toUpperCase(); final OmrFileFilter filter = new OmrFileFilter( resources.getString("bookAnd") + " " + resources.getString("majorImageFiles") + " (" + OMR.BOOK_EXTENSION + " " + suffixes + ")", allSuffixes.split("\\s")); return selectPaths(false, startPath, filter); } //----------------// // selectBookPath // //----------------// /** * Let the user interactively select a book path. * * @param save true for write, false for read * @param startPath starting path * @return the selected path or null */ public static Path selectBookPath (boolean save, Path startPath) { return selectPath(save, startPath, filter(OMR.BOOK_EXTENSION)); } //-----------------// // selectBookPaths // //-----------------// /** * Let the user interactively select book paths. * * @param save true for write, false for read * @param startPath starting path * @return the array of selected paths, perhaps empty but not null */ public static Path[] selectBookPaths (boolean save, Path startPath) { return selectPaths(save, startPath, filter(OMR.BOOK_EXTENSION)); } //-----------------// // selectImagePath // //-----------------// public static Path selectImagePath () { final String suffixes = constants.validImageExtensions.getValue(); final String allSuffixes = suffixes + " " + suffixes.toUpperCase(); final OmrFileFilter filter = new OmrFileFilter( resources.getString("majorImageFiles") + " (" + suffixes + ")", allSuffixes.split("\\s")); return selectPath(false, Paths.get(BookManager.getDefaultImageFolder()), filter); } //------------------// // selectImagePaths // //------------------// public static Path[] selectImagePaths () { final String suffixes = constants.validImageExtensions.getValue(); final String allSuffixes = suffixes + " " + suffixes.toUpperCase(); final OmrFileFilter filter = new OmrFileFilter( resources.getString("majorImageFiles") + " (" + suffixes + ")", allSuffixes.split("\\s")); return selectPaths(false, Paths.get(BookManager.getDefaultImageFolder()), filter); } //------------// // selectPath // //------------// /** * Let the user interactively select a path. * * @param save true for write, false for read * (for read, path will be checked to exist and not be a directory) * @param startPath the starting path * @param filter filter on file extensions * @return the selected path or null */ public static Path selectPath (boolean save, Path startPath, OmrFileFilter filter) { final Path path = UIUtil.pathChooser(save, OMR.gui.getFrame(), startPath, filter); if (path == null) { return null; } if (save) { return path; } if (!Files.exists(path)) { logger.warn(format(resources.getString("fileNotFound.pattern"), path)); return null; } if (Files.isDirectory(path)) { logger.warn(format(resources.getString("isDirectory.pattern"), path)); return null; } return path; } //-------------// // selectPaths // //-------------// /** * Let the user interactively select an array of paths. * * @param save true for write, false for read * (for read, path will be checked to exist and not be a directory) * @param startPath the starting path * @param filter filter on file extensions * @return the array of selected paths, perhaps empty but not null */ public static Path[] selectPaths (boolean save, Path startPath, OmrFileFilter filter) { final Path[] paths = UIUtil.pathsChooser(save, OMR.gui.getFrame(), startPath, filter); if (paths.length == 0) { return paths; } if (save) { return paths; } for (Path path : paths) { if (!Files.exists(path)) { logger.warn(format(resources.getString("fileNotFound.pattern"), path)); return new Path[0]; } if (Files.isDirectory(path)) { logger.warn(format(resources.getString("isDirectory.pattern"), path)); return new Path[0]; } } return paths; } //~ Inner Classes ------------------------------------------------------------------------------ //---------------// // CloseBookTask // //---------------// private static class CloseBookTask extends VoidTask { final SheetStub stub; /** * Create an asynchronous task to close the book. * * @param stub the current stub of the book to close */ CloseBookTask (SheetStub stub) { this.stub = stub; } @Override protected Void doInBackground () throws InterruptedException { final Book book = stub.getBook(); try { LogUtil.start(book); book.close(stub.getNumber()); } catch (Throwable ex) { logger.warn("Error in CloseBookTask {}", ex.toString(), ex); } finally { LogUtil.stopBook(); } return null; } } //-----------// // Constants // //-----------// private static class Constants extends ConstantSet { private final Constant.String validImageExtensions = new Constant.String( ".bmp .gif .jpg .jpeg .png .tiff .tif .pdf", "Valid image file extensions, whitespace-separated"); private final Constant.Boolean closeConfirmation = new Constant.Boolean( true, "Should we ask confirmation for closing an unsaved book?"); private final Constant.Boolean preOpenBookParameters = new Constant.Boolean( false, "Automatically open book parameters dialog at book creation?"); } //----------------// // ExportBookTask // //----------------// private static class ExportBookTask extends VoidTask { final Book book; /** * Create an asynchronous task to export the book. * * @param book the book to export * @param bookPathSansExt (non-null) the target export book path with no extension */ ExportBookTask (Book book, Path bookPathSansExt) { this.book = book; book.setExportPathSansExt(bookPathSansExt); } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(book); if (checkParameters(book)) { book.export(book.getValidSelectedStubs(), book.getScores()); } } catch (Throwable ex) { logger.warn("Error in ExportBookTask {}", ex.toString(), ex); } finally { LogUtil.stopBook(); } return null; } } //-----------------// // ExportSheetTask // //-----------------// private static class ExportSheetTask extends VoidTask { final Sheet sheet; final Path sheetExportPath; ExportSheetTask (Sheet sheet, Path sheetExportPath) { this.sheet = sheet; this.sheetExportPath = sheetExportPath; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(sheet.getStub()); if (checkParameters(sheet)) { sheet.getStub().reachStep(OmrStep.PAGE, false); sheet.export(sheetExportPath); } } catch (StepPause sp) { logger.info("ExportSheetTask stopped by user."); } catch (Exception ex) { logger.warn("Error in ExportSheetTask {}", ex.toString(), ex); } finally { LogUtil.stopStub(); } return null; } } //--------------------// // ExportBookMidiTask // //--------------------// private static class ExportBookMidiTask extends VoidTask { final Book book; final Path bookPathSansExt; ExportBookMidiTask (Book book, Path bookPathSansExt) { this.book = book; this.bookPathSansExt = bookPathSansExt; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(book); final boolean swap = (OMR.gui == null) || Main.getCli().isSwap() || swapProcessedSheets(); final boolean ok = book.transcribe( book.getValidSelectedStubs(), book.getScores(), swap); if (!ok) { logger.warn("MIDI export aborted: transcription incomplete"); return null; } final List scores = book.getScores(); final boolean multi = scores.size() > 1; final String ext = MidiAbstractions.MIDI_EXTENSION; for (Score score : scores) { final String suffix = multi ? (OMR.MOVEMENT_EXTENSION + score.getId()) : ""; final Path target = bookPathSansExt.resolveSibling( bookPathSansExt.getFileName().toString() + suffix + ext); try { new MidiExporter(score).export(target); logger.info("MIDI exported to {}", target); } catch (Exception ex) { logger.warn("Could not export MIDI for movement {}: {}", score.getId(), ex.toString(), ex); } } } catch (Throwable ex) { logger.warn("Error in ExportBookMidiTask {}", ex.toString(), ex); } finally { LogUtil.stopBook(); } return null; } } //---------------------// // ExportSheetMidiTask // //---------------------// private static class ExportSheetMidiTask extends VoidTask { final Sheet sheet; final Path sheetExportPath; ExportSheetMidiTask (Sheet sheet, Path sheetExportPath) { this.sheet = sheet; this.sheetExportPath = sheetExportPath; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(sheet.getStub()); if (!checkParameters(sheet)) { return null; } sheet.getStub().reachStep(OmrStep.PAGE, false); final SheetStub stub = sheet.getStub(); final Book book = stub.getBook(); final List pageRefs = stub.getPageRefs(); if (pageRefs.isEmpty()) { logger.warn("Sheet has no pages; nothing to export"); return null; } final Path folder = sheetExportPath.getParent(); if (folder != null && !Files.exists(folder)) { Files.createDirectories(folder); } final String ext = MidiAbstractions.MIDI_EXTENSION; final String baseName = stripExtension( sheetExportPath.getFileName().toString(), ext); if (pageRefs.size() > 1) { for (PageRef pageRef : pageRefs) { final Score score = new Score(); score.setBook(book); score.addPageNumber(stub.getNumber(), pageRef); new ScoreReduction(score).reduce(Arrays.asList(stub)); final String movementName = baseName + OMR.MOVEMENT_EXTENSION + pageRef.getId(); final Path target = sheetExportPath.resolveSibling(movementName + ext); new MidiExporter(score).export(target); logger.info("MIDI exported to {}", target); } } else { final Score score = new Score(); score.setBook(book); score.addPageNumber(stub.getNumber(), stub.getFirstPageRef()); new ScoreReduction(score).reduce(Arrays.asList(stub)); final Path target = sheetExportPath.resolveSibling(baseName + ext); new MidiExporter(score).export(target); logger.info("MIDI exported to {}", target); } } catch (StepPause sp) { logger.info("ExportSheetMidiTask stopped by user."); } catch (Exception ex) { logger.warn("Error in ExportSheetMidiTask {}", ex.toString(), ex); } finally { LogUtil.stopStub(); } return null; } private static String stripExtension (String name, String ext) { return name.toLowerCase().endsWith(ext) ? name.substring(0, name.length() - ext.length()) : name; } } //--------------// // ExportFormat // //--------------// private enum ExportFormat { MUSIC_XML, MIDI } //--------------// // ExportTarget // //--------------// private static final class ExportTarget { final Path path; final ExportFormat format; ExportTarget (Path path, ExportFormat format) { this.path = path; this.format = format; } } //---------------// // LazySingleton // //---------------// private static class LazySingleton { static final BookActions INSTANCE = new BookActions(); private LazySingleton () { } } //--------------// // LoadBookTask // //--------------// /** * Task that loads a book (.omr project) file in background. *

* If load is successful, GUI allocates all stub tabs and then focuses on: *

    *
  1. Either the desired stub if its number is provided, *
  2. Or the first valid stub encountered. *
*/ public static class LoadBookTask extends WaitingTask { /** Underlying path. */ protected Path path; /** Desired sheet number, if any. */ protected Integer sheetNumber; // Constructor needed for creation of HistoryMenu public LoadBookTask () { super(OmrGui.getApplication(), "Loading"); } /** * Creates a new LoadBookTaskobject, with a plain book path * * @param path plain book path */ public LoadBookTask (Path path) { super(OmrGui.getApplication(), "Loading " + path + " ..."); this.path = path; } /** * Creates a new LoadBookTaskobject, with a desired sheet number. * * @param sheetPath the desired sheet number to focus upon */ public LoadBookTask (SheetPath sheetPath) { super(OmrGui.getApplication(), "Loading " + sheetPath.getBookPath() + " ..."); path = sheetPath.getBookPath(); sheetNumber = sheetPath.getSheetNumber(); } @Override protected Book doInBackground () throws Exception { Book book = null; try { // Actually open the book zip system book = OMR.engine.loadBook(path); } catch (Throwable ex) { logger.warn("Error in {} {}", getClass().getSimpleName(), ex.toString(), ex); } return book; } /** * Set the path value. * * @param path the path used by the task */ public void setPath (Path path) { this.path = path; } /** * Set the sheet path value (path#sheet) . * * @param sheetPath the sheet path used by the task */ public void setPath (SheetPath sheetPath) { setPath(sheetPath.getBookPath()); sheetNumber = sheetPath.getSheetNumber(); } @Override protected void succeeded (Book book) { if (book != null) { final StubsController controller = StubsController.getInstance(); // Insert all tabs in GUI stubsPane controller.displayStubs(book, sheetNumber); // Select stub final SheetStub stub = (sheetNumber != null) ? book.getStub(sheetNumber) : book.getFirstValidStub(); controller.selectAssembly(stub); } } } //---------------// // LoadFilesTask // //---------------// /** * Task that loads a list of files (possibly a mix of book files and input files). */ public static class LoadFilesTask extends PathListTask, Void> { public LoadFilesTask (Collection paths) { super(paths); } @Override protected List doInBackground () throws Exception { final List bookList = new ArrayList<>(); for (Path path : pathList) { Book book = null; try { final String ext = FileUtil.getExtension(path.getFileName()); if (ext.equalsIgnoreCase(OMR.BOOK_EXTENSION)) { // Book file book = OMR.engine.loadBook(path); } else { // Assumed image file book = OMR.engine.loadInput(path); book.createStubs(); // Read just the count of sheets in input file } } catch (Throwable ex) { logger.warn("Error in {} {}", getClass().getSimpleName(), ex.toString(), ex); } bookList.add(book); // book can be null } return bookList; } @Override protected void succeeded (List bookList) { final StubsController controller = StubsController.getInstance(); for (Book book : bookList) { if (book != null) { try { // Insert all tabs in GUI stubsPane controller.displayStubs(book, null); // Select stub controller.selectAssembly(book.getFirstValidStub()); if (book.isImage()) { // Pre-open Book Parameters dialog on this brand new book? if (preOpenBookParameters()) { BookActions.applyUserSettings(book.getFirstValidStub()); } } } catch (Throwable ex) { logger.warn( "Error in {} {}", getClass().getSimpleName(), ex.toString(), ex); } } } } } //---------------// // LoadImageTask // //---------------// /** * Task that opens an image file. */ public static class LoadImageTask extends LoadBookTask { // Constructor needed for creation of HistoryMenu public LoadImageTask () { } /** * Creates a LoadBookTask object on an input file assumed to exist. * * @param path the path to input file */ public LoadImageTask (Path path) { super(path); } @Override protected Book doInBackground () throws InterruptedException { try { // Actually open the image file Book book = OMR.engine.loadInput(path); LogUtil.start(book); book.createStubs(); // Read just the count of sheets in input file return book; } catch (Exception ex) { logger.warn("Error opening path " + path + " " + ex, ex); } finally { LogUtil.stopBook(); } return null; } @Override protected void succeeded (Book book) { if (book != null) { try { super.succeeded(book); // Pre-open Book Parameters dialog on this brand new book? if (preOpenBookParameters()) { BookActions.applyUserSettings(book.getFirstValidStub()); } } catch (Throwable ex) { logger.warn("Error in {} {}", getClass().getSimpleName(), ex.toString(), ex); } } } } //---------------// // PrintBookTask // //---------------// public static class PrintBookTask extends VoidTask { final Book book; final Path bookPrintPath; public PrintBookTask (Book book, Path bookPrintPath) { this.book = book; this.bookPrintPath = bookPrintPath; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(book); book.setPrintPath(bookPrintPath); book.print(book.getValidSelectedStubs()); } catch (Throwable ex) { logger.warn("Error in PrintBookTask {}", ex.toString(), ex); } finally { LogUtil.stopBook(); } return null; } } //----------------// // PrintSheetTask // //----------------// public static class PrintSheetTask extends VoidTask { final Sheet sheet; final Path sheetPrintPath; public PrintSheetTask (Sheet sheet, Path sheetPrintPath) { this.sheet = sheet; this.sheetPrintPath = sheetPrintPath; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(sheet.getStub()); sheet.print(sheetPrintPath); } catch (Throwable ex) { logger.warn("Error in PrintSheetTask {}", ex.toString(), ex); } finally { LogUtil.stopStub(); } return null; } } //------------------------// // PromptOnClosingUnsaved // //------------------------// private static class PromptOnClosingUnsaved extends Param { public PromptOnClosingUnsaved () { super(GLOBAL_SCOPE); } @Override public Boolean getSpecific () { if (isSpecific()) { return getValue(); } else { return null; } } @Override public Boolean getValue () { return constants.closeConfirmation.getValue(); } @Override public boolean isSpecific () { return !constants.closeConfirmation.isSourceValue(); } @Override public boolean setSpecific (Boolean specific) { if (!getValue().equals(specific)) { constants.closeConfirmation.setValue(specific); logger.info(resources.getString("promptOnClose." + specific)); return true; } else { return false; } } } //---------------// // ResetBookTask // //---------------// /** * Task that resets all valid selected sheets of book to gray or binary. */ private class ResetBookTask extends VoidTask { final Book book; final OmrStep step; public ResetBookTask (Book book, OmrStep step) { this.book = book; this.step = step; } @Override protected Void doInBackground () throws Exception { book.resetTo(step); setBookTranscribable(true); setBookModifiedOrUpgraded(true); return null; } } //----------------// // SampleBookTask // //----------------// private static class SampleBookTask extends VoidTask { final Book book; SampleBookTask (Book book) { this.book = book; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(book); book.sample(book.getValidSelectedStubs()); } catch (Throwable ex) { logger.warn("Error in SampleBookTask {}", ex.toString(), ex); } finally { LogUtil.stopBook(); } return null; } } //-----------------// // SampleSheetTask // //-----------------// public static class SampleSheetTask extends VoidTask { final Sheet sheet; public SampleSheetTask (Sheet sheet) { this.sheet = sheet; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(sheet.getStub()); sheet.sample(); } catch (Throwable ex) { logger.warn("Error in SampleSheetTask {}", ex.toString(), ex); } finally { LogUtil.stopStub(); } return null; } } //---------------// // StoreBookTask // //---------------// private static class StoreBookTask extends VoidTask { final Book book; final Path bookPath; /** * Create an asynchronous task to store the book. * * @param book the book to export * @param bookPath (non-null) the target to store book path */ StoreBookTask (Book book, Path bookPath) { this.book = book; this.bookPath = bookPath; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(book); final Path oldBookPath = book.getBookPath(); book.store(bookPath, false); BookActions.getInstance().setBookModifiedOrUpgraded(false); // Update books map OMR.engine.renameBook(book, oldBookPath); } catch (Throwable ex) { logger.warn("Error in StoreBookTask {}", ex.toString(), ex); } finally { LogUtil.stopBook(); } return null; } } //--------------------// // TranscribeBookTask // //--------------------// private static class TranscribeBookTask extends VoidTask { private final SheetStub stub; private final Book book; private final boolean swap; TranscribeBookTask (SheetStub stub, boolean swap) { this.stub = stub; this.book = stub.getBook(); this.swap = swap; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(book); if (swap) { book.store(BookManager.getDefaultSavePath(book), false); } book.transcribe(book.getValidSelectedStubs(), book.getScores(), swap); } catch (Throwable ex) { logger.warn("Could not transcribe book {}", ex.toString(), ex); } finally { LogUtil.stopBook(); } return null; } @Override protected void succeeded (Void result) { StubsController.getInstance().callAboutStub(stub); } } //---------------------// // TranscribeSheetTask // //---------------------// private static class TranscribeSheetTask extends VoidTask { private final Sheet sheet; TranscribeSheetTask (Sheet sheet) { this.sheet = sheet; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(sheet.getStub()); sheet.getStub().transcribe(); } catch (Throwable ex) { logger.warn("Could not transcribe sheet {}", ex.toString(), ex); } finally { LogUtil.stopStub(); } return null; } } //-----------------// // UpgradeBookTask // //-----------------// private static class UpgradeBookTask extends VoidTask { private final Book book; UpgradeBookTask (Book book) { this.book = book; } @Override protected Void doInBackground () throws InterruptedException { try { LogUtil.start(book); book.upgradeStubs(); } catch (Throwable ex) { logger.warn("Error while upgrading book {}", ex.toString(), ex); } finally { LogUtil.stopBook(); } return null; } } }