diff --git a/build.gradle b/build.gradle index 7f3e900..14e9caf 100644 --- a/build.gradle +++ b/build.gradle @@ -72,8 +72,8 @@ dependencies { compile 'com.googlecode.ajui:ajui:1.0.0' compile 'org.daisy.dotify:dotify.api:4.1.1' - compile 'org.daisy.streamline:streamline-api:1.0.1' - compile 'org.daisy.dotify:dotify.common:4.1.0' + compile 'org.daisy.streamline:streamline-api:1.1.0' + compile 'org.daisy.dotify:dotify.common:4.2.0' compile ('org.daisy.streamline:streamline-engine:1.1.0') { exclude module: 'streamline-api' } diff --git a/src/META-INF/services/org.daisy.streamline.api.config.UserConfigurationsProvider b/src/META-INF/services/org.daisy.streamline.api.config.UserConfigurationsProvider new file mode 100644 index 0000000..3557503 --- /dev/null +++ b/src/META-INF/services/org.daisy.streamline.api.config.UserConfigurationsProvider @@ -0,0 +1 @@ +application.ui.impl.template.UserConfigurationsImpl \ No newline at end of file diff --git a/src/application/common/FeatureSwitch.java b/src/application/common/FeatureSwitch.java index bab358f..83e9f47 100644 --- a/src/application/common/FeatureSwitch.java +++ b/src/application/common/FeatureSwitch.java @@ -22,7 +22,12 @@ public enum FeatureSwitch { /** * When on, the progress indicator in the Dotify panel uses progress values reported from the task system */ - REPORT_PROGRESS("on".equalsIgnoreCase(System.getProperty("application.feature.report-progress", "off"))); + REPORT_PROGRESS("on".equalsIgnoreCase(System.getProperty("application.feature.report-progress", "off"))), + /** + * When on, the template dialog is opened on import + */ + TEMPLATE_DIALOG_ON_IMPORT("on".equalsIgnoreCase(System.getProperty("application.feature.template-dialog-on-import", "on"))), + ; private final boolean on; FeatureSwitch(boolean on) { diff --git a/src/application/common/Singleton.java b/src/application/common/Singleton.java new file mode 100644 index 0000000..4d30064 --- /dev/null +++ b/src/application/common/Singleton.java @@ -0,0 +1,19 @@ +package application.common; + +import org.daisy.streamline.api.config.ConfigurationsCatalog; +import org.daisy.streamline.api.config.ConfigurationsCatalogService; + +public enum Singleton { + INSTANCE; + public static Singleton getInstance() { + return Singleton.INSTANCE; + } + + private ConfigurationsCatalogService configsCatalog; + public synchronized ConfigurationsCatalogService getConfigurationsCatalog() { + if (configsCatalog==null) { + configsCatalog = ConfigurationsCatalog.newInstance(); + } + return configsCatalog; + } +} diff --git a/src/application/l10n/Messages.java b/src/application/l10n/Messages.java index 0b2648c..35c6010 100644 --- a/src/application/l10n/Messages.java +++ b/src/application/l10n/Messages.java @@ -19,6 +19,7 @@ public enum Messages { APPLICATION_ENVIRONMENT("application-environment"), BUTTON_OK("button-ok"), BUTTON_SELECT("button-select"), + BUTTON_DELETE("button-delete"), EMBOSS_WINDOW_TITLE("emboss-window-title"), /** * The file is invalid and cannot be embossed. @@ -83,6 +84,7 @@ public enum Messages { TITLE_EXPORT_DIALOG("title-export-dialog"), TITLE_SAVE_AS_DIALOG("title-save-as-dialog"), TITLE_TEMPLATES_DIALOG("title-templates-dialog"), + TITLE_SAVE_TEMPLATES_DIALOG("title-save-template-dialog"), LABEL_BRAILLE_FONT("label-braille-font"), LABEL_TEXT_FONT("label-text-font"), LABEL_TRANSLATION("label-translation"), @@ -100,7 +102,7 @@ public enum Messages { LABEL_SETUP_INVALID("label-setup-invalid"), LABEL_CREATE_TEST_DOCUMENT("label-create-test-document"), LABEL_PAPER_DIMENSIONS("label-paper-dimensions"), - LABEL_NONE("label-none"), + LABEL_EMPTY("label-empty"), LABEL_HEIGHT("label-height"), LABEL_WIDTH("label-width"), LABEL_ROLL_SIZE("label-roll-size"), @@ -123,7 +125,7 @@ public enum Messages { /** * Confirm delete a custom paper */ - MESSAGE_CONFIRM_DELETE_PAPER("message-confirm-delete-paper"), + MESSAGE_CONFIRM_DELETE("message-confirm-delete"), MESSAGE_FILE_MODIFIED_BY_ANOTHER_APPLICATION("message-file-modified-by-another-application"), MESSAGE_CONFIRM_SAVE_MALFORMED_XML("message-confirm-save-malformed-xml"), MESSAGE_CONFIRM_QUIT_UNSAVED_CHANGES("message-confirm-quit-unsaved-changes"), @@ -140,8 +142,8 @@ public enum Messages { TOOLTIP_NO_VOLUME_SUPPORT("tooltip-no-volume-support"), TOOLTIP_ACCURATE_LINE_SPACING("tooltip-accurate-line-spacing"), TOOLTIP_SIMPLE_LINE_SPACING("tooltip-simple-line-spacing"), - TOOLTIP_SHOW_OPTIONS("tooltip-show-options"), - TOOLTIP_HIDE_OPTIONS("tooltip-hide-options"), + TOOLTIP_SHOW_CONVERTER("tooltip-show-converter"), + TOOLTIP_HIDE_CONVERTER("tooltip-hide-converter"), VALUE_USE_DEFAULT("value-use-default"), EXTENSION_FILTER_SUPPORTED_FILES("extension-filter-supported-files"), EXTENSION_FILTER_ALL_FILES("extension-filter-all-files"), diff --git a/src/application/l10n/messages.properties b/src/application/l10n/messages.properties index 78622f1..65346b4 100644 --- a/src/application/l10n/messages.properties +++ b/src/application/l10n/messages.properties @@ -12,6 +12,7 @@ button-select=Select button-add=Add button-cancel=Cancel button-emboss=Emboss +button-delete=Delete emboss-window-title=Emboss error-cannot-emboss-invalid-file=Cannot emboss, file has errors error-failed-to-parse-page-range=Failed to parse page range: {0} @@ -37,6 +38,7 @@ title-import-source-document-dialog=Import source document title-export-dialog=Export title-save-as-dialog=Save As... title-templates-dialog=Select template ({0}) +title-save-template-dialog=Save template label-braille-font=Braille font label-text-font=Text font label-target-locale=Target locale @@ -89,6 +91,7 @@ label-line-numbers=Line numbers label-merge-order=Merge order label-target-format=Target format label-make=Make +label-empty=Empty message-six-dot-only=6-dot only message-search-result={0} by {1} message-unknown-author=Unknown @@ -96,7 +99,7 @@ message-unknown-title=Untitled message-paper-details={0} - {1} ({2}: {3}) message-book-dimensions={0} cells wide x {1} cells high message-file-sent-to-embosser=File has been sent to embosser -message-confirm-delete-paper=Do you really want to delete {0}? +message-confirm-delete=Do you really want to delete {0}? message-file-modified-by-another-application=The file has been modified by another program. Do you want to reload it and lose the changes made here? message-confirm-save-malformed-xml=The contents is not well-formed XML. Do you want to save anyway? message-confirm-quit-unsaved-changes=Some open files have been modified but not saved. Do you want to quit anyway? @@ -129,6 +132,11 @@ menu-item-toggle-viewing-mode=Toggle viewing mode menu-item-activate-view=Activate view menu-item-next-editor=Next editor menu-item-previous-editor=Previous editor +menu-item-show-converter=Show +menu-item-apply-template=Apply template... +menu-item-save-template=Save template... +menu-item-converter=Converter +menu-item-update=Update preferences-window-title=Preferences tab-emboss=Emboss tab-general=General @@ -142,8 +150,8 @@ tooltip-volume-support=Supports volumes tooltip-no-volume-support=No support for volumes tooltip-accurate-line-spacing=Supports accurate line spacing tooltip-simple-line-spacing=Supports simple line spacing only -tooltip-show-options=Show options -tooltip-hide-options=Hide options +tooltip-show-converter=Show converter +tooltip-hide-converter=Hide converter tooltip-correct-formatting=Correct formatting tooltip-move-up=Move the selected file up (Alt + Up) tooltip-move-down=Move the selected file down (Alt + Down) diff --git a/src/application/l10n/messages_no.properties b/src/application/l10n/messages_no.properties index 8e9cc26..3d9bd83 100644 --- a/src/application/l10n/messages_no.properties +++ b/src/application/l10n/messages_no.properties @@ -12,6 +12,7 @@ button-select=Velg button-add=Legg til button-cancel=Avbryt button-emboss=Skriv ut +button-delete=Slette emboss-window-title=Skriv ut error-cannot-emboss-invalid-file=Kan ikke skrive ut, filen inneholder feil error-failed-to-parse-page-range=Klarte ikke å tolke sidespenn: {0} @@ -37,6 +38,7 @@ title-import-source-document-dialog=Importer kildedokument title-export-dialog=Eksporter title-save-as-dialog=Lagre som... title-templates-dialog=Velg mal ({0}) +title-save-template-dialog=Lagre mal label-braille-font=Punktskriftfont label-text-font=Tekstfont label-target-locale=Målsted @@ -88,6 +90,7 @@ label-word-wrap=Tekstbryting label-line-numbers=Linjenummer label-target-format=Målformat label-make=Make +label-empty=Tom message-six-dot-only=Bare 6-punkt label-merge-order=Merge order message-search-result={0} av {1} @@ -96,7 +99,7 @@ message-unknown-title=ingen tittel message-paper-details={0} - {1} ({2}: {3}) message-book-dimensions={0} celler bred x {1} celler høy message-file-sent-to-embosser=Filen har blitt sendt til punktskriftskriveren -message-confirm-delete-paper=Er du sikker på at du vil slette {0}? +message-confirm-delete=Er du sikker på at du vil slette {0}? message-file-modified-by-another-application=Filen har blitt endret av et annet program. Vil du laste den inn på nytt og miste endringene du har gjort her? message-confirm-save-malformed-xml=Innholdet er ikke velformet XML. Vil du lagre alikevel? message-confirm-quit-unsaved-changes=Noen filer har blitt endret men ikke lagret. Vil du avslutte alikevel? @@ -129,6 +132,11 @@ menu-item-toggle-viewing-mode=Toggle viewing mode menu-item-activate-view=Activate view menu-item-next-editor=Neste redigeringsprogram menu-item-previous-editor=Forrige redigeringsprogram +menu-item-show-converter=Vis +menu-item-apply-template=Bruk mal... +menu-item-save-template=Lagre mal... +menu-item-converter=Converter +menu-item-update=Oppdater preferences-window-title=Innstillinger tab-emboss=Skriv ut tab-general=Generelt @@ -142,8 +150,8 @@ tooltip-volume-support=St tooltip-no-volume-support=Støtter ikke hefter tooltip-accurate-line-spacing=Supports accurate line spacing tooltip-simple-line-spacing=Supports simple line spacing only -tooltip-show-options=Vis alternativer -tooltip-hide-options=Skjul alternativer +tooltip-show-converter=Show converter +tooltip-hide-converter=Hide converter tooltip-correct-formatting=Rett formatering tooltip-move-up=Move the selected file up (Alt + Up) tooltip-move-down=Move the selected file down (Alt + Down) diff --git a/src/application/l10n/messages_sv.properties b/src/application/l10n/messages_sv.properties index feeb885..fe03f7a 100644 --- a/src/application/l10n/messages_sv.properties +++ b/src/application/l10n/messages_sv.properties @@ -12,6 +12,7 @@ button-select=V button-add=Lägg till button-cancel=Avbryt button-emboss=Skriv ut +button-delete=Ta bort emboss-window-title=Skriv ut error-cannot-emboss-invalid-file=Kan inte skriva ut, filen innehåller fel error-failed-to-parse-page-range=Kunde inte tolka sidomfånget: {0} @@ -37,6 +38,7 @@ title-import-source-document-dialog=Importera k title-export-dialog=Exportera title-save-as-dialog=Spara som... title-templates-dialog=Välj mall ({0}) +title-save-template-dialog=Spara mall label-braille-font=Punktskriftsfont label-text-font=Textfont label-target-locale=Målplats @@ -89,6 +91,7 @@ label-line-numbers=Radnummer label-merge-order=Sammanslagningsordning label-target-format=Målformat label-make=Fabrikat +label-empty=Tom message-six-dot-only=Endast 6-punkt message-search-result={0} av {1} message-unknown-author=okänd @@ -96,7 +99,7 @@ message-unknown-title=ingen titel message-paper-details={0} - {1} ({2}: {3}) message-book-dimensions=Bredd {0} celler, höjd {1} celler message-file-sent-to-embosser=Filen har skickats till punktskriftsskrivaren -message-confirm-delete-paper=Vill du verkligen ta bort {0}? +message-confirm-delete=Vill du verkligen ta bort {0}? message-file-modified-by-another-application=Filen har ändrats av ett annat program. Vill du ladda om den och förlora ändringarna som gjorts här? message-confirm-save-malformed-xml=Innehållet är inte välformad XML. Vill du spara ändå? message-confirm-quit-unsaved-changes=Vissa öppna filer har ändrats men inte sparats. Vill du avsluta ändå? @@ -129,6 +132,11 @@ menu-item-toggle-viewing-mode=V menu-item-next-editor=Nästa editor menu-item-activate-view=Aktivera vy menu-item-previous-editor=Föregående editor +menu-item-show-converter=Visa +menu-item-apply-template=Tillämpa mall... +menu-item-save-template=Spara mall... +menu-item-converter=Konverterare +menu-item-update=Uppdatera preferences-window-title=Inställningar tab-emboss=Skriv ut tab-general=Allmänt @@ -142,8 +150,8 @@ tooltip-volume-support=St tooltip-no-volume-support=Inget stöd för volymer tooltip-accurate-line-spacing=Stödjer noggrant radavstånd tooltip-simple-line-spacing=Stödjer endast enkelt radavstånd -tooltip-show-options=Visa alternativ -tooltip-hide-options=Dölj alternativ +tooltip-show-converter=Visa konverterare +tooltip-hide-converter=Dölj konverterare tooltip-correct-formatting=Rätta formatering tooltip-move-up=Flytta vald fil uppåt (Alt + Upp) tooltip-move-down=Flytta vald fil nedåt (Alt + Ner) diff --git a/src/application/ui/Main.fxml b/src/application/ui/Main.fxml index cbf1c42..646683c 100644 --- a/src/application/ui/Main.fxml +++ b/src/application/ui/Main.fxml @@ -57,7 +57,7 @@ - + @@ -106,12 +106,28 @@ - - - - + + + + + + + + + + + + + + + + + + + + @@ -124,6 +140,11 @@ + + + + + diff --git a/src/application/ui/MainController.java b/src/application/ui/MainController.java index 7f7c6e0..c4110eb 100644 --- a/src/application/ui/MainController.java +++ b/src/application/ui/MainController.java @@ -11,6 +11,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -27,6 +28,7 @@ import org.daisy.braille.utils.pef.PEFFileMerger; import org.daisy.braille.utils.pef.PEFFileMerger.SortType; import org.daisy.braille.utils.pef.TextHandler; +import org.daisy.dotify.studio.api.Converter; import org.daisy.dotify.studio.api.Editor; import org.daisy.dotify.studio.api.ExportActionDescription; import org.daisy.dotify.studio.api.ExportActionMaker; @@ -67,6 +69,7 @@ import javafx.scene.control.ButtonType; import javafx.scene.control.CheckMenuItem; import javafx.scene.control.Menu; +import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; import javafx.scene.control.ScrollPane; import javafx.scene.control.SingleSelectionModel; @@ -110,20 +113,27 @@ public class MainController { @FXML private WebView console; @FXML private ScrollPane consoleScroll; @FXML private MenuItem closeMenuItem; + @FXML private MenuBar topMenuBar; + @FXML private Menu convertMenu; @FXML private Menu exportMenu; @FXML private MenuItem saveMenuItem; @FXML private MenuItem saveAsMenuItem; @FXML private MenuItem refreshMenuItem; @FXML private MenuItem openInBrowserMenuItem; @FXML private MenuItem embossMenuItem; + @FXML private CheckMenuItem showConverterMenuItem; @FXML private CheckMenuItem showSearchMenuItem; @FXML private CheckMenuItem showConsoleMenuItem; + @FXML private CheckMenuItem watchSourceMenuItem; @FXML private ToggleButton scrollLockButton; @FXML private MenuItem nextEditorViewMenuItem; @FXML private MenuItem previousEditorViewMenuItem; @FXML private MenuItem toggleViewMenuItem; @FXML private MenuItem viewingModeMenuItem; @FXML private MenuItem activateViewMenuItem; + @FXML private MenuItem refreshConverterMenuItem; + @FXML private MenuItem applyTemplateMenuItem; + @FXML private MenuItem saveTemplateMenuItem; private final double dividerPosition = 0.2; private Tab searchTab; private Tab helpTab; @@ -204,53 +214,13 @@ public class MainController { canSave = new SimpleBooleanProperty(); canToggleView = new SimpleBooleanProperty(); urlProperty = new SimpleStringProperty(); + topMenuBar.getMenus().remove(convertMenu); //add menu bindings - setMenuBindings(); + Platform.runLater(()->setMenuBindings()); } private void setMenuBindings() { - tabPane.getSelectionModel().selectedItemProperty().addListener((o, ov, nv)->{ - canEmboss.unbind(); - canExport.unbind(); - canSave.unbind(); - canToggleView.unbind(); - urlProperty.unbind(); - exportMenu.getItems().clear(); - if (nv!=null && nv.getContent() instanceof Editor) { - Editor p = ((Editor)nv.getContent()); - canEmboss.bind(p.canEmbossProperty()); - canExport.bind(p.fileDetailsProperty().mediaTypeProperty().isEqualTo(FileDetailsCatalog.PEF_FORMAT.getMediaType())); - canSave.bind(p.canSaveProperty()); - canToggleView.bind(p.toggleViewProperty()); - urlProperty.bind(p.urlProperty()); - for (ExportActionDescription ead : exportActions.listActions(p.getFileDetails())) { - exportActions.newExportAction(ead.getIdentifier()) - .ifPresent(ea->{ - MenuItem it = new MenuItem(ead.getName()); - it.setOnAction(v->{ - Tab t = tabPane.getSelectionModel().getSelectedItem(); - if (nv!=t) { - throw new AssertionError("Internal error"); - } - try { - p.export(root.getScene().getWindow(), ea); - } catch (IOException e) { - logger.log(Level.WARNING, "Export failed.", e); - Alert alert = new Alert(AlertType.ERROR, e.toString(), ButtonType.OK); - alert.showAndWait(); - } - }); - exportMenu.getItems().add(it); - }); - } - } else { - canEmboss.set(false); - canExport.set(false); - canSave.set(false); - canToggleView.set(false); - urlProperty.set(null); - } - }); + tabPane.getSelectionModel().selectedItemProperty().addListener((o, ov, nv)->updateTab(ov, nv)); BooleanBinding noTabBinding = tabPane.getSelectionModel().selectedItemProperty().isNull(); BooleanBinding noTabExceptHelpBinding = noTabBinding.or( tabPane.getSelectionModel().selectedItemProperty().isEqualTo(helpTab) @@ -273,6 +243,72 @@ private void setMenuBindings() { embossMenuItem.disableProperty().bind(noTabExceptHelpBinding.or(canEmboss.not())); } + private void updateTab(Tab ov, Tab nv) { + canEmboss.unbind(); + canExport.unbind(); + canSave.unbind(); + canToggleView.unbind(); + urlProperty.unbind(); + refreshConverterMenuItem.disableProperty().unbind(); + applyTemplateMenuItem.disableProperty().unbind(); + saveTemplateMenuItem.disableProperty().unbind(); + exportMenu.getItems().clear(); + topMenuBar.getMenus().remove(convertMenu); + if (ov!=null && ov.getContent() instanceof Editor) { + Editor p = ((Editor)ov.getContent()); + p.getConverter().ifPresent(v->{ + showConverterMenuItem.selectedProperty().unbindBidirectional(v.showOptionsProperty()); + watchSourceMenuItem.selectedProperty().unbindBidirectional(v.watchSourceProperty()); + }); + } + if (nv!=null && nv.getContent() instanceof Editor) { + Editor p = ((Editor)nv.getContent()); + canExport.bind(p.fileDetailsProperty().mediaTypeProperty().isEqualTo(FileDetailsCatalog.PEF_FORMAT.getMediaType())); + canEmboss.bind(p.canEmbossProperty()); + canSave.bind(p.canSaveProperty()); + canToggleView.bind(p.toggleViewProperty()); + urlProperty.bind(p.urlProperty()); + if (p.getConverter().isPresent()) { + Converter v = p.getConverter().get(); + showConverterMenuItem.selectedProperty().bindBidirectional(v.showOptionsProperty()); + watchSourceMenuItem.selectedProperty().bindBidirectional(v.watchSourceProperty()); + BooleanBinding refreshDisableBinding = Bindings.not(v.isIdleProperty()); + refreshConverterMenuItem.disableProperty().bind(refreshDisableBinding); + applyTemplateMenuItem.disableProperty().bind(refreshDisableBinding); + saveTemplateMenuItem.disableProperty().bind(refreshDisableBinding); + } + for (ExportActionDescription ead : exportActions.listActions(p.getFileDetails())) { + exportActions.newExportAction(ead.getIdentifier()) + .ifPresent(ea->{ + MenuItem it = new MenuItem(ead.getName()); + it.setOnAction(v->{ + Tab t = tabPane.getSelectionModel().getSelectedItem(); + if (nv!=t) { + throw new AssertionError("Internal error"); + } + try { + p.export(root.getScene().getWindow(), ea); + } catch (IOException e) { + logger.log(Level.WARNING, "Export failed.", e); + Alert alert = new Alert(AlertType.ERROR, e.toString(), ButtonType.OK); + alert.showAndWait(); + } + }); + exportMenu.getItems().add(it); + }); + } + if (p.getConverter().isPresent()) { + topMenuBar.getMenus().add(2, convertMenu); + } + } else { + canEmboss.set(false); + canExport.set(false); + canSave.set(false); + canToggleView.set(false); + urlProperty.set(null); + } + } + private static boolean canDropFiles(List files) { List exts = newImportExtensionStream() .map(val -> "."+val.toLowerCase()) @@ -393,7 +429,7 @@ private Optional getSelectedPreview() { getSelectedPreview().ifPresent(v->v.activate()); } - @FXML void refresh() { + @FXML void refresh() { Tab t = tabPane.getSelectionModel().getSelectedItem(); if (t!=null) { javafx.scene.Node node = t.getContent(); @@ -403,9 +439,21 @@ private Optional getSelectedPreview() { ((Editor)node).reload(); } } - } - - @FXML void openInBrowser() { + } + + @FXML void refreshConverter() { + getSelectedPreview().flatMap(v->v.getConverter()).ifPresent(v->v.apply()); + } + + @FXML void saveTemplate() { + getSelectedPreview().flatMap(v->v.getConverter()).ifPresent(v->v.saveTemplate()); + } + + @FXML void applyTemplate() { + getSelectedPreview().flatMap(v->v.getConverter()).ifPresent(v->v.selectTemplateAndApply()); + } + + @FXML void openInBrowser() { if (Desktop.isDesktopSupported()) { Tab t = tabPane.getSelectionModel().getSelectedItem(); if (t!=null) { @@ -485,6 +533,7 @@ private Optional getSelectedPreview() { controller.closing(); // update contents of tab setTab(t, selected.getName(), StartupDetails.open(selected), controller.getOptions()); + updateTab(t, t); // TODO: Restore document position } } catch (IOException e) { @@ -747,19 +796,22 @@ private static String toFilesDesc(String ext) { ?ext2.substring(0, 1).toUpperCase() + ext2.substring(1) :ext2.toUpperCase()); } - - private void selectTemplateAndOpen(File selected) { - // choose template - TemplateView dialog = new TemplateView(selected); - if (dialog.hasTemplates()) { + + private void selectTemplateAndOpen(File selected) { + TemplateView dialog = null; + if (FeatureSwitch.TEMPLATE_DIALOG_ON_IMPORT.isOn() && (dialog = new TemplateView(selected)).hasTemplates()) { + // choose template dialog.initOwner(root.getScene().getWindow()); dialog.initModality(Modality.APPLICATION_MODAL); dialog.showAndWait(); + if (dialog.getSelectedConfiguration().isPresent()) { + // convert then add tab + addSourceTab(selected, dialog.getSelectedConfiguration().get()); + } + } else { + addSourceTab(selected, Collections.emptyMap()); } - - // convert then add tab - addSourceTab(selected, dialog.getSelectedConfiguration()); - } + } @FXML void closeApplication() { Stage stage = ((Stage)root.getScene().getWindow()); diff --git a/src/application/ui/impl/template/ExclusiveAccessImpl.java b/src/application/ui/impl/template/ExclusiveAccessImpl.java new file mode 100644 index 0000000..50ef685 --- /dev/null +++ b/src/application/ui/impl/template/ExclusiveAccessImpl.java @@ -0,0 +1,31 @@ +package application.ui.impl.template; + +import java.io.File; + +import org.daisy.dotify.common.io.InterProcessLock; +import org.daisy.dotify.common.io.LockException; +import org.daisy.streamline.api.config.ExclusiveAccess; +import org.daisy.streamline.api.config.ExclusiveAccessException; + +class ExclusiveAccessImpl implements ExclusiveAccess { + private final InterProcessLock lock; + + public ExclusiveAccessImpl(File lockFile) { + this.lock = new InterProcessLock(lockFile); + } + + @Override + public boolean acquire() { + try { + return lock.lock(); + } catch (LockException e) { + throw new ExclusiveAccessException(e); + } + } + + @Override + public void release() { + lock.unlock(); + } + +} diff --git a/src/application/ui/impl/template/UserConfigurationsImpl.java b/src/application/ui/impl/template/UserConfigurationsImpl.java new file mode 100644 index 0000000..4031d20 --- /dev/null +++ b/src/application/ui/impl/template/UserConfigurationsImpl.java @@ -0,0 +1,70 @@ +package application.ui.impl.template; + +import java.io.File; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.daisy.streamline.api.config.ConfigurationDetails; +import org.daisy.streamline.api.config.ConfigurationsProviderException; +import org.daisy.streamline.api.config.ExclusiveAccess; +import org.daisy.streamline.api.config.UserConfigurationsCollection; +import org.daisy.streamline.api.config.UserConfigurationsProvider; + +public class UserConfigurationsImpl implements UserConfigurationsProvider { + + @Override + public Set getConfigurationDetails() { + return getInstance().getConfigurationDetails(); + } + + @Override + public Map getConfiguration(String key) throws ConfigurationsProviderException { + return getInstance().getConfiguration(key); + } + + @Override + public Optional addConfiguration(String niceName, String description, Map config) { + return getInstance().addConfiguration(niceName, description, config); + } + + @Override + public boolean removeConfiguration(String identifier) { + return getInstance().removeConfiguration(identifier); + } + + @Override + public boolean containsConfiguration(String identifier) { + return getInstance().containsConfiguration(identifier); + } + + private enum InstanceManager { + GET(configureCollection()); + private final UserConfigurationsCollection collection; + private InstanceManager(UserConfigurationsCollection collection) { + this.collection = collection; + } + + private static UserConfigurationsCollection configureCollection() { + File configDir = getConfigDir(); + configDir.mkdirs(); + ExclusiveAccess lock = new ExclusiveAccessImpl(new File(configDir, "lock")); + return new UserConfigurationsCollection(configDir, lock); + } + + private static File getConfigDir() { + String userHome = System.getProperty("user.home"); + // Note that modifying this path will effectively clear all existing user templates + return new File(new File(new File(userHome, ".dotify"), "data"), "config"); + } + } + + /** + * Gets the instance. + * @return returns the instance + */ + private static UserConfigurationsCollection getInstance() { + return InstanceManager.GET.collection; + } + +} diff --git a/src/application/ui/prefs/PaperSettingsController.java b/src/application/ui/prefs/PaperSettingsController.java index 1fd7691..5a315e5 100644 --- a/src/application/ui/prefs/PaperSettingsController.java +++ b/src/application/ui/prefs/PaperSettingsController.java @@ -79,7 +79,7 @@ public PaperSettingsController() { list.setOnKeyTyped(ev -> { if ("\u007F".equals(ev.getCharacter())) { //DEL PaperAdapter pa = list.getSelectionModel().getSelectedItem(); - Alert alert = new Alert(AlertType.CONFIRMATION, Messages.MESSAGE_CONFIRM_DELETE_PAPER.localize(pa.getDisplayName())); + Alert alert = new Alert(AlertType.CONFIRMATION, Messages.MESSAGE_CONFIRM_DELETE.localize(pa.getDisplayName())); alert.showAndWait() .filter(response -> response == ButtonType.OK) .ifPresent(response -> { diff --git a/src/application/ui/preview/Dotify.fxml b/src/application/ui/preview/Dotify.fxml index 38295b3..810c2ae 100644 --- a/src/application/ui/preview/Dotify.fxml +++ b/src/application/ui/preview/Dotify.fxml @@ -42,7 +42,7 @@ - diff --git a/src/application/ui/preview/DotifyController.java b/src/application/ui/preview/DotifyController.java index d5f2c62..89d65a6 100644 --- a/src/application/ui/preview/DotifyController.java +++ b/src/application/ui/preview/DotifyController.java @@ -16,8 +16,10 @@ import java.util.logging.Logger; import java.util.stream.Collectors; +import org.daisy.dotify.studio.api.Converter; import org.daisy.streamline.api.media.AnnotatedFile; import org.daisy.streamline.api.option.UserOption; +import org.daisy.streamline.api.option.UserOptionValue; import org.daisy.streamline.api.tasks.CompiledTaskSystem; import org.daisy.streamline.api.tasks.InternalTask; import org.daisy.streamline.api.tasks.TaskSystem; @@ -27,8 +29,13 @@ import application.common.BuildInfo; import application.common.FeatureSwitch; +import application.common.Singleton; import application.l10n.Messages; +import application.ui.template.TemplateView; import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -46,14 +53,16 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Paint; import javafx.scene.text.Font; +import javafx.stage.Modality; /** * Provides a controller for Dotify. * @author Joel HÃ¥kansson * */ -public class DotifyController extends BorderPane { +public class DotifyController extends BorderPane implements Converter { private static final Logger logger = Logger.getLogger(DotifyController.class.getCanonicalName()); + private final Wrapper> overrideParameters = new Wrapper<>(); @FXML private ScrollPane options; @FXML private VBox tools; @FXML private Button toggle; @@ -61,10 +70,13 @@ public class DotifyController extends BorderPane { @FXML private Button applyButton; @FXML private CheckBox monitorCheckbox; @FXML private ProgressIndicator progress; + private final File input; private boolean refreshRequested; private Set values; private boolean closing; private ExecutorService exeService; + private BooleanProperty showOptions; + private BooleanProperty canRequestUpdate; /** * Creates a new options controller. @@ -85,21 +97,39 @@ public DotifyController(AnnotatedFile selected, File out, String tag, String out } catch (IOException e) { logger.log(Level.WARNING, "Failed to load view", e); } + this.input = selected.getFile(); refreshRequested = false; closing = false; exeService = Executors.newWorkStealingPool(); + showOptions = new SimpleBooleanProperty(true); + showOptions.addListener((o, ov, nv)->{ + if (nv.booleanValue()) { + setBottom(tools); + setCenter(this.options); + toggle.setText("<"); + toggle.getTooltip().setText(Messages.TOOLTIP_HIDE_CONVERTER.localize()); + } else { + setBottom(null); + setCenter(null); + toggle.setText(">"); + toggle.getTooltip().setText(Messages.TOOLTIP_SHOW_CONVERTER.localize()); + } + }); + canRequestUpdate = new SimpleBooleanProperty(false); setRunning(0); DotifyTask dt = new DotifyTask(selected, out, tag, outputFormat, options); dt.setOnSucceeded(ev -> { Consumer resultWatcher = onSuccess.apply(out); DotifyResult dr = dt.getValue(); setOptions(dr.getTaskSystem(), dr.getResults(), options); + canRequestUpdate.set(true); setRunning(1); Thread th = new Thread(new SourceDocumentWatcher(selected, out, tag, outputFormat, resultWatcher)); th.setDaemon(true); th.start(); }); dt.setOnFailed(ev->{ + canRequestUpdate.set(true); setRunning(1); logger.log(Level.WARNING, "Import failed.", dt.getException()); Alert alert = new Alert(AlertType.ERROR, dt.getException().toString(), ButtonType.OK); @@ -184,32 +214,90 @@ public Map getParams() { } public void setParams(Map opts) { + Map optsCopy = new HashMap<>(opts); for (Node n : vbox.getChildren()) { if (n instanceof OptionItem) { OptionItem o = (OptionItem)n; - Object value = opts.get(o.getKey()); + Object value = optsCopy.remove(o.getKey()); if (value!=null) { o.setValue(value.toString()); } } } + displayItems("Template", optsCopy.entrySet().stream() + .map(v->new UserOption.Builder(v.getKey()).defaultValue("").addValue(new UserOptionValue.Builder(v.getValue().toString()).build()).build()) + .collect(Collectors.toList()), optsCopy); + } + + @Override + public void selectTemplateAndApply() { + TemplateView dialog = new TemplateView(input); + dialog.initOwner(getScene().getWindow()); + dialog.initModality(Modality.APPLICATION_MODAL); + dialog.showAndWait(); + if (dialog.getSelectedConfiguration().isPresent()) { + synchronized (overrideParameters) { + overrideParameters.setValue(dialog.getSelectedConfiguration().get()); + } + vbox.getChildren().clear(); + requestRefresh(); + } + } + + @Override + public void apply() { + requestRefresh(); } + @Override + public boolean getWatchSource() { + return monitorCheckbox.selectedProperty().get(); + } + + @Override + public void setWatchSource(boolean value) { + monitorCheckbox.selectedProperty().set(value); + } + + @Override + public BooleanProperty watchSourceProperty() { + return monitorCheckbox.selectedProperty(); + } + + @FXML void requestRefresh() { refreshRequested = true; + canRequestUpdate.set(false); + setRunning(0); } - @FXML void toggleOptions() { - if (getBottom()==tools) { - setBottom(null); - setCenter(null); - toggle.setText(">"); - toggle.getTooltip().setText(Messages.TOOLTIP_SHOW_OPTIONS.localize()); - } else { - setBottom(tools); - setCenter(options); - toggle.setText("<"); - toggle.getTooltip().setText(Messages.TOOLTIP_HIDE_OPTIONS.localize()); + @Override + public final boolean getShowOptions() { + return showOptions.get(); + } + + @Override + public final void setShowOptions(boolean value) { + showOptions.set(value); + } + + @FXML public void toggleOptions() { + setShowOptions(getBottom()!=tools); + } + + @Override + public BooleanProperty showOptionsProperty() { + return showOptions; + } + + @Override public void saveTemplate() { + TemplateDetailsView dialog = new TemplateDetailsView(); + dialog.initOwner(this.getScene().getWindow()); + dialog.initModality(Modality.APPLICATION_MODAL); + dialog.showAndWait(); + if (dialog.getResult().isPresent()) { + NameDesc nameDesc = dialog.getResult().get(); + Singleton.getInstance().getConfigurationsCatalog().addConfiguration(nameDesc.getName(), nameDesc.getDesc(), getParams()); } } @@ -275,11 +363,25 @@ void performAction() { try { isRunning = true; setRunning(0); - Map opts = getParams(); + Map opts; + // Using a local variable as intermediary storage here to avoid synchronization + // on the else statement + Map overrides = null; + synchronized(overrideParameters) { + overrides = overrideParameters.getValue(); + overrideParameters.setValue(null); + } + if (overrides!=null) { + opts = overrides; + } else { + opts = getParams(); + } + DotifyTask dt = new DotifyTask(annotatedInput, output, locale, outputFormat, opts); dt.setOnFailed(ev->{ isRunning = false; setRunning(1); + canRequestUpdate.set(true); logger.log(Level.WARNING, "Update failed.", dt.getException()); Alert alert = new Alert(AlertType.ERROR, dt.getException().toString(), ButtonType.OK); alert.showAndWait(); @@ -287,6 +389,7 @@ void performAction() { dt.setOnSucceeded(ev -> { isRunning = false; setRunning(1); + canRequestUpdate.set(true); Platform.runLater(() -> { if (resultWatcher!=null) { resultWatcher.accept(output); @@ -377,4 +480,14 @@ private List getResults() { } } + @Override + public ReadOnlyBooleanProperty isIdleProperty() { + return canRequestUpdate; + } + + @Override + public boolean getIsIdle() { + return canRequestUpdate.get(); + } + } diff --git a/src/application/ui/preview/EditorWrapperController.java b/src/application/ui/preview/EditorWrapperController.java index c537518..e54fb6d 100644 --- a/src/application/ui/preview/EditorWrapperController.java +++ b/src/application/ui/preview/EditorWrapperController.java @@ -5,7 +5,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; +import org.daisy.dotify.studio.api.Converter; import org.daisy.dotify.studio.api.Editor; import org.daisy.dotify.studio.api.ExportAction; import org.daisy.dotify.studio.api.FileDetailsProperty; @@ -19,6 +21,7 @@ import application.common.Settings.Keys; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.scene.Node; import javafx.scene.layout.BorderPane; import javafx.stage.FileChooser.ExtensionFilter; @@ -26,7 +29,7 @@ public class EditorWrapperController extends BorderPane implements Editor { private final Editor impl; - private DotifyController dotify; + private final DotifyController dotify; private EditorWrapperController(Editor impl, DotifyController converter) { this.impl = impl; @@ -182,4 +185,9 @@ public FileDetailsProperty fileDetailsProperty() { return impl.fileDetailsProperty(); } + @Override + public Optional getConverter() { + return Optional.ofNullable(dotify); + } + } diff --git a/src/application/ui/preview/NameDesc.java b/src/application/ui/preview/NameDesc.java new file mode 100644 index 0000000..0f3327d --- /dev/null +++ b/src/application/ui/preview/NameDesc.java @@ -0,0 +1,21 @@ +package application.ui.preview; + +class NameDesc { + private final String name; + private final String desc; + + NameDesc(String name, String desc) { + this.name = name; + this.desc = desc; + } + + public String getName() { + return name; + } + public String getDesc() { + return desc; + } + + + +} diff --git a/src/application/ui/preview/TemplateDetails.fxml b/src/application/ui/preview/TemplateDetails.fxml new file mode 100644 index 0000000..7c236a0 --- /dev/null +++ b/src/application/ui/preview/TemplateDetails.fxml @@ -0,0 +1,27 @@ + + + + + + + + + + + + +