package com.kodcu.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.install4j.api.launcher.ApplicationLauncher; import com.kodcu.animation.GifExporterFX; import com.kodcu.component.*; import com.kodcu.config.*; import com.kodcu.engine.AsciidocConverterProvider; import com.kodcu.engine.AsciidocNashornConverter; import com.kodcu.engine.AsciidocWebkitConverter; import com.kodcu.keyboard.KeyHelper; import com.kodcu.logging.MyLog; import com.kodcu.logging.TableViewLogAppender; import com.kodcu.other.*; import com.kodcu.outline.Section; import com.kodcu.service.*; import com.kodcu.service.convert.DocumentConverter; import com.kodcu.service.convert.GitbookToAsciibookService; import com.kodcu.service.convert.docbook.DocBookConverter; import com.kodcu.service.convert.ebook.EpubConverter; import com.kodcu.service.convert.ebook.MobiConverter; import com.kodcu.service.convert.html.HtmlBookConverter; import com.kodcu.service.convert.markdown.MarkdownService; import com.kodcu.service.convert.odf.ODFConverter; import com.kodcu.service.convert.slide.SlideConverter; import com.kodcu.service.extension.MathJaxService; import com.kodcu.service.extension.PlantUmlService; import com.kodcu.service.extension.TreeService; import com.kodcu.service.extension.chart.ChartProvider; import com.kodcu.service.shortcut.ShortcutProvider; import com.kodcu.service.table.AsciidocTableController; import com.kodcu.service.ui.FileBrowseService; import com.kodcu.service.ui.IndikatorService; import com.kodcu.service.ui.TabService; import com.kodcu.service.ui.TooltipTimeFixService; import com.kodcu.spell.dictionary.DictionaryService; import com.sun.javafx.stage.StageHelper; import com.terminalfx.TerminalBuilder; import com.terminalfx.TerminalTab; import com.terminalfx.config.TerminalConfig; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.application.HostServices; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.event.ActionEvent; import javafx.event.Event; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.geometry.Insets; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.TextFieldTreeCell; import javafx.scene.image.Image; import javafx.scene.input.*; import javafx.scene.layout.*; import javafx.scene.text.Text; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.util.Duration; import netscape.javascript.JSObject; import org.kordamp.ikonli.fontawesome.FontAwesome; import org.kordamp.ikonli.javafx.FontIcon; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import org.springframework.web.util.UriComponentsBuilder; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.CodeSource; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.nio.file.StandardOpenOption.*; @Component public class ApplicationController extends TextWebSocketHandler implements Initializable { public Label goUpLabel; public VBox terminalLeftBox; public TabPane terminalTabPane; public ToggleButton workdirToggle; public ShowerHider leftShowerHider; public ShowerHider rightShowerHider; public ShowerHider bottomShowerHider; public HBox terminalHBox; public SplitPane mainVerticalSplitPane; public ToggleButton logToggleButton; public ToggleButton terminalToggleButton; public ToggleGroup leftToggleGroup; public VBox rightTooglesBox; public VBox configBox; public ToggleGroup rightToggleGroup; public ToggleButton toggleConfigButton; public Label basicSearch; public Button newTerminalButton; public Button closeTerminalButton; public Button recordTerminalButton; private Logger logger = LoggerFactory.getLogger(ApplicationController.class); public Label odfPro = new Label(); public VBox logVBox; public Label statusText; public SplitPane editorSplitPane; public Label statusMessage; public MenuItem newFolder; public MenuItem newSlide; public Menu newMenu; public ProgressBar progressBar; public Menu favoriteDirMenu; public MenuItem addToFavoriteDir; public MenuItem afxVersionItem; public MenuItem renameFile; public MenuItem newFile; public TabPane tabPane; public SplitPane splitPane; public SplitPane splitPaneVertical; public TreeView<Item> fileSystemView; public Label workingDirButton; public Label refreshLabel; public AnchorPane rootAnchor; public ProgressIndicator indikator; public ListView<Item> recentListView; public MenuItem openFileTreeItem; public MenuItem deletePathItem; public MenuItem openFolderTreeItem; public MenuItem openFileListItem; public MenuItem openFolderListItem; public MenuItem copyPathListItem; public MenuItem copyTreeItem; public MenuItem copyListItem; public MenuButton leftButton; public Label htmlPro; public Label pdfPro; public Label ebookPro; public Label docbookPro; public Label browserPro; public SeparatorMenuItem renameSeparator; public SeparatorMenuItem addToFavSeparator; private AnchorPane markdownTableAnchor; private Stage markdownTableStage; public TreeView<Section> outlineTreeView; private Path userHome = Paths.get(System.getProperty("user.home")); private TreeSet<Section> outlineList = new TreeSet<>(); private ObservableList<DocumentMode> modeList = FXCollections.observableArrayList(); private final Pattern bookArticleHeaderRegex = Pattern.compile("^:doctype:.*(book|article)", Pattern.MULTILINE); private final Pattern forceIncludeRegex = Pattern.compile("^:forceinclude:", Pattern.MULTILINE); private BooleanProperty stopRendering = new SimpleBooleanProperty(false); private AtomicBoolean includeAsciidocResource = new AtomicBoolean(false); private static ObservableList<MyLog> logList = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); @Autowired public HtmlPane htmlPane; @Autowired public AsciidocWebkitConverter asciidocWebkitConverter; @Autowired private EditorConfigBean editorConfigBean; @Autowired private TerminalConfigBean terminalConfigBean; @Autowired private LocationConfigBean locationConfigBean; @Autowired private PreviewConfigBean previewConfigBean; @Autowired private ApplicationContext applicationContext; @Autowired private ConfigurationService configurationService; @Autowired private AsciidocTableController asciidocTableController; @Autowired private TreeService treeService; @Autowired private TooltipTimeFixService tooltipTimeFixService; @Autowired private TabService tabService; @Autowired private ODFConverter odfConverter; @Autowired private PlantUmlService plantUmlService; @Autowired private MathJaxService mathJaxService; @Autowired private DocBookConverter docBookConverter; @Autowired private HtmlBookConverter htmlBookService; @Autowired @Qualifier("pdfBookConverter") private DocumentConverter pdfBookConverter; @Autowired private EpubConverter epubConverter; @Autowired private Current current; @Autowired private FileBrowseService fileBrowser; @Autowired private IndikatorService indikatorService; @Autowired private MobiConverter mobiConverter; @Autowired private SampleBookService sampleBookService; @Autowired private EmbeddedWebApplicationContext server; @Autowired private ParserService parserService; @Autowired private DirectoryService directoryService; @Autowired private ThreadService threadService; @Autowired private ShortcutProvider shortcutProvider; @Autowired private RestTemplate restTemplate; @Autowired private Base64.Encoder base64Encoder; @Autowired private ChartProvider chartProvider; @Autowired private DictionaryService dictionaryService; @Autowired private SpellcheckConfigBean spellcheckConfigBean; private Stage stage; private List<WebSocketSession> sessionList = new ArrayList<>(); private Scene scene; private AnchorPane asciidocTableAnchor; private Stage asciidocTableStage; private int port = 8080; private Path configPath; @Value("${application.version}") private String version; @Value("${application.issue}") private String issuePage; @Value("${application.forum}") private String issueForum; @Value("${application.gitter}") private String gitterChat; @Value("${application.github}") private String githubPage; @Autowired private SlideConverter slideConverter; @Autowired private SlidePane slidePane; private Path installationPath; private String logPath; @Value("${application.config.folder}") private String configDirName; @Autowired private LiveReloadPane liveReloadPane; private List<String> supportedModes; @Autowired private FileWatchService fileWatchService; private Timeline progressBarTimeline = null; @Autowired private StoredConfigBean storedConfigBean; @Autowired private AsciidocConverterProvider converterProvider; @Value("${application.worker.url}") private String workerUrl; @Value("${application.preview.url}") private String previewUrl; @Value("${application.mathjax.url}") private String mathjaxUrl; @Autowired private AsciidocNashornConverter nashornEngineConverter; @Value("${application.live.url}") private String liveUrl; private ConverterResult lastConverterResult; private HostServices hostServices; @Value("${application.donation}") private String donationUrl; private ToggleGroup configToggleGroup; private Scene asciidocTableScene; private Scene markdownTableScene; public void createAsciidocTable() { asciidocTableStage.showAndWait(); } public void createMarkdownTable() { markdownTableStage.showAndWait(); } @FXML private void fullScreen(ActionEvent event) { getStage().setFullScreen(!getStage().isFullScreen()); } @FXML private void directoryView(ActionEvent event) { splitPane.setDividerPositions(0.1610294117647059, 0.5823529411764706); } private void generatePdf() { this.generatePdf(false); } private void generatePdf(boolean askPath) { if (!current.currentPath().isPresent()) saveDoc(); threadService.runTaskLater(() -> { pdfBookConverter.convert(askPath); }); } @FXML private void generateSampleBook(ActionEvent event) { DirectoryChooser directoryChooser = directoryService.newDirectoryChooser("Select a New Directory for sample book"); File file = directoryChooser.showDialog(null); threadService.runTaskLater(() -> { sampleBookService.produceSampleBook(configPath, file.toPath()); directoryService.setWorkingDirectory(Optional.of(file.toPath())); fileBrowser.browse(file.toPath()); threadService.runActionLater(() -> { directoryView(null); tabService.addTab(file.toPath().resolve("book.adoc")); }); }); } public void convertDocbook() { convertDocbook(false); } public void convertDocbook(boolean askPath) { if (!current.currentPath().isPresent()) saveDoc(); threadService.runTaskLater(() -> { indikatorService.startProgressBar(); Path docbookPath = directoryService.getSaveOutputPath(ExtensionFilters.DOCBOOK, askPath); Consumer<String> step = docbook -> { final String finalDocbook = docbook; threadService.runTaskLater(() -> { IOHelper.writeToFile(docbookPath, finalDocbook, CREATE, TRUNCATE_EXISTING, WRITE); }); threadService.runActionLater(() -> { ObservableList<Item> recentFiles = storedConfigBean.getRecentFiles(); recentFiles.remove(new Item(docbookPath)); recentFiles.add(0, new Item(docbookPath)); }); indikatorService.stopProgressBar(); }; docBookConverter.convert(false, step); }); } private void convertEpub() { convertEpub(false); } private void convertEpub(boolean askPath) { threadService.runTaskLater(() -> { epubConverter.produceEpub3(askPath); }); } @WebkitCall(from = "asciidoctor-math") public void math(String formula, String type, String imagesDir, String imageTarget, String nodename) { mathJaxService.processFormula(formula, imagesDir, imageTarget); } @WebkitCall(from = "mathjax.html") public void snapshotFormula(String formula, String imagesDir, String imageTarget) { mathJaxService.snapshotFormula(formula, imagesDir, imageTarget); } private void convertMobi() { convertMobi(false); } private void convertMobi(boolean askPath) { if (Objects.nonNull(locationConfigBean.getKindlegen())) { if (!Files.exists(Paths.get(locationConfigBean.getKindlegen()))) { locationConfigBean.setKindlegen(null); } } if (Objects.isNull(locationConfigBean.getKindlegen())) { FileChooser fileChooser = directoryService.newFileChooser("Select 'kindlegen' executable"); File kindlegenFile = fileChooser.showOpenDialog(null); if (Objects.isNull(kindlegenFile)) return; locationConfigBean.setKindlegen(kindlegenFile.toPath().toString()); } threadService.runTaskLater(() -> { mobiConverter.convert(askPath); }); } private void generateHtml() { this.generateHtml(false); } private void generateHtml(boolean askPath) { if (!current.currentPath().isPresent()) this.saveDoc(); threadService.runTaskLater(() -> { htmlBookService.convert(askPath); }); } public void tree(String content, String type, String imagesDir, String imageTarget, String nodename) { if (content.split("#").length > content.split("\\|-").length) { createFileTree(content, type, imagesDir, imageTarget, nodename); } else { createHighlightFileTree(content, type, imagesDir, imageTarget, nodename); } } public void createFileTree(String tree, String type, String imagesDir, String imageTarget, String nodename) { threadService.runTaskLater(() -> { treeService.createFileTree(tree, type, imagesDir, imageTarget, nodename); }); } public void createHighlightFileTree(String tree, String type, String imagesDir, String imageTarget, String nodename) { threadService.runTaskLater(() -> { treeService.createHighlightFileTree(tree, type, imagesDir, imageTarget, nodename); }); } @FXML public void refreshWorkingDir() { Optional<Path> currentPath = current.currentPath().map(Path::getParent); if (currentPath.isPresent()) { directoryService.changeWorkigDir(currentPath.get()); } else { fileBrowser.refresh(); } } @FXML public void goHome() { directoryService.changeWorkigDir(userHome); } @WebkitCall public void imageToBase64Url(final String url, final int index) { threadService.runTaskLater(() -> { try { byte[] imageBuffer = restTemplate.getForObject(url, byte[].class); String imageBase64 = base64Encoder.encodeToString(imageBuffer); htmlPane.updateBase64Url(index, imageBase64); } catch (Exception e) { logger.error("Problem occured while converting image to base64 for {}", url); } }); } public void stageWidthChanged(ObservableValue observable, Number oldValue, Number newValue) { if (!terminalToggleButton.isSelected() && !logToggleButton.isSelected()) { mainVerticalSplitPane.setDividerPosition(0, 1); } } @Override public void initialize(URL url, ResourceBundle rb) { } public void initializeApp() { port = server.getEmbeddedServletContainer().getPort(); checkDuplicatedJars(); initializeNashornConverter(); initializeTerminal(); terminalTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { Optional.ofNullable(newValue) .map(e -> ((TerminalTab) e)) .filter(TerminalTab::isReady) .ifPresent(TerminalTab::focusCursor); }); Arrays.asList(htmlPane, slidePane, liveReloadPane).forEach(viewPanel -> VBox.getVgrow(viewPanel)); threadService.runTaskLater(() -> { while (true) { try { renderLoop(); } catch (Exception e) { logger.error(e.getMessage(), e); } } }); progressBar.prefWidthProperty().bind(rightShowerHider.widthProperty()); progressBarTimeline = new Timeline( new KeyFrame( Duration.ZERO, new KeyValue(progressBar.progressProperty(), 0) ), new KeyFrame( Duration.seconds(15), new KeyValue(progressBar.progressProperty(), 1) )); threadService.runActionLater(() -> { rightShowerHider.setMaster(htmlPane); }, true); initializeLogViewer(); initializeDoctypes(); tooltipTimeFixService.fix(); basicSearch.visibleProperty().bind(fileSystemView.focusedProperty().and(basicSearch.textProperty().isNotEmpty())); basicSearch.visibleProperty().addListener((observable, oldValue, newValue) -> { if (!newValue) { basicSearch.setText(""); } }); AtomicBoolean dontKeyType = new AtomicBoolean(true); fileSystemView.addEventHandler(KeyEvent.KEY_PRESSED, event -> { dontKeyType.set(true); if (KeyHelper.isBackSpace(event)) { if (basicSearch.isVisible()) { event.consume(); Optional.ofNullable(basicSearch.getText()) .filter(t -> t.length() > 0) .map(t -> t.substring(0, t.length() - 1)) .ifPresent(s -> { basicSearch.setText(s); fileBrowser.searchAndSelect(basicSearch.getText()); }); } } else if (KeyHelper.isUp(event)) { if (basicSearch.isVisible()) { event.consume(); fileBrowser.searchDownAndSelect(basicSearch.getText()); } } else if (KeyHelper.isDown(event)) { if (basicSearch.isVisible()) { event.consume(); fileBrowser.searchUpAndSelect(basicSearch.getText()); } } else if (KeyHelper.isDelete(event)) { event.consume(); deleteSelectedItems(event); } else if (KeyHelper.isCopy(event)) { event.consume(); this.copyFiles(tabService.getSelectedTabPaths()); } else if (KeyHelper.isF2(event)) { event.consume(); this.renameFile(event); } else if (KeyHelper.isEnter(event)) { event.consume(); TreeItem<Item> selectedItem = fileSystemView.getSelectionModel().getSelectedItem(); if (Objects.isNull(selectedItem)) return; Path selectedPath = selectedItem.getValue().getPath(); tabService.previewDocument(selectedPath); } if (event.isConsumed()) { dontKeyType.set(false); } }); fileSystemView.addEventHandler(KeyEvent.KEY_TYPED, event -> { if (dontKeyType.get()) { String eventText = Optional.ofNullable(event.getText()) .filter(e -> !e.isEmpty()) .orElseGet(() -> { String character = event.getCharacter(); char[] chars = character.toCharArray(); if (chars.length > 0) { String text = new String(chars); if (chars.length == 1) { if (chars[0] == ' ') { return text; } return text.trim(); } else { return text; } } return character; }); if (!eventText.isEmpty()) { event.consume(); basicSearch.setText(basicSearch.getText() + eventText); fileBrowser.searchAndSelect(basicSearch.getText()); } } }); afxVersionItem.setText(String.join(" ", "Version", version)); ContextMenu htmlProMenu = new ContextMenu(); htmlProMenu.getStyleClass().add("build-menu"); htmlPro.setContextMenu(htmlProMenu); htmlPro.setOnMouseClicked(event -> { htmlProMenu.show(htmlPro, event.getScreenX(), 50); }); htmlProMenu.getItems().add(MenuItemBuilt.item("Save").click(event -> { this.generateHtml(); })); htmlProMenu.getItems().add(MenuItemBuilt.item("Save as").click(event -> { this.generateHtml(true); })); htmlProMenu.getItems().add(MenuItemBuilt.item("Copy source").tip("Copy HTML source").click(event -> { this.cutCopy(lastConverterResult.getRendered()); })); htmlProMenu.getItems().add(MenuItemBuilt.item("Clone source").tip("Copy HTML source (Embedded images)").click(event -> { htmlPane.call("imageToBase64Url", new Object[]{}); })); ContextMenu pdfProMenu = new ContextMenu(); pdfProMenu.getStyleClass().add("build-menu"); pdfProMenu.getItems().add(MenuItemBuilt.item("Save").click(event -> { this.generatePdf(); })); pdfProMenu.getItems().add(MenuItemBuilt.item("Save as").click(event -> { this.generatePdf(true); })); pdfPro.setContextMenu(pdfProMenu); pdfPro.setOnMouseClicked(event -> { pdfProMenu.show(pdfPro, event.getScreenX(), 50); }); ContextMenu docbookProMenu = new ContextMenu(); docbookProMenu.getStyleClass().add("build-menu"); docbookProMenu.getItems().add(MenuItemBuilt.item("Save").click(event -> { this.convertDocbook(); })); docbookProMenu.getItems().add(MenuItemBuilt.item("Save as").click(event -> { this.convertDocbook(true); })); docbookPro.setContextMenu(docbookProMenu); docbookPro.setOnMouseClicked(event -> { docbookProMenu.show(docbookPro, event.getScreenX(), 50); }); ContextMenu ebookProMenu = new ContextMenu(); ebookProMenu.getStyleClass().add("build-menu"); ebookProMenu.getItems().add(MenuBuilt.name("Mobi") .add(MenuItemBuilt.item("Save").click(event -> { this.convertMobi(); })) .add(MenuItemBuilt.item("Save as").click(event -> { this.convertMobi(true); })).build()); ebookProMenu.getItems().add(MenuBuilt.name("Epub") .add(MenuItemBuilt.item("Save").click(event -> { this.convertEpub(); })) .add(MenuItemBuilt.item("Save as").click(event -> { this.convertEpub(true); })).build()); ebookPro.setOnMouseClicked(event -> { ebookProMenu.show(ebookPro, event.getScreenX(), 50); }); ebookPro.setContextMenu(ebookProMenu); ContextMenu odfProMenu = new ContextMenu(); odfProMenu.getStyleClass().add("build-menu"); odfProMenu.setAutoHide(true); odfProMenu.getItems().add(MenuItemBuilt.item("Save").click(event -> { odfProMenu.hide(); this.generateODFDocument(); })); odfProMenu.getItems().add(MenuItemBuilt.item("Save as").click(event -> { odfProMenu.hide(); this.generateODFDocument(true); })); odfPro.setContextMenu(odfProMenu); odfPro.setOnMouseClicked(event -> { odfProMenu.show(odfPro, event.getScreenX(), 50); }); browserPro.setOnMouseClicked(event -> { if (event.getButton() == MouseButton.PRIMARY) this.externalBrowse(); }); fileSystemView.setCellFactory(param -> { TreeCell<Item> cell = new TextFieldTreeCell<Item>(); cell.setOnDragDetected(event -> { Dragboard db = cell.startDragAndDrop(TransferMode.ANY); ClipboardContent content = new ClipboardContent(); content.putFiles(Arrays.asList(cell.getTreeItem().getValue().getPath().toFile())); db.setContent(content); }); return cell; }); liveReloadPane.webEngine().setOnAlert(event -> { if ("LIVE_LOADED".equals(event.getData())) { liveReloadPane.setMember("afx", this); // current.currentEditor().rerender(); } }); htmlPane.webEngine().setOnAlert(event -> { if ("PREVIEW_LOADED".equals(event.getData())) { htmlPane.setMember("afx", this); current.currentEditor().rerender(); } }); asciidocWebkitConverter.webEngine().setOnAlert(event -> { if ("WORKER_LOADED".equals(event.getData())) { asciidocWebkitConverter.setMember("afx", this); htmlPane.load(String.format(previewUrl, port, directoryService.interPath())); } }); asciidocWebkitConverter.load(String.format(workerUrl, port)); openFileTreeItem.setOnAction(event -> { ObservableList<TreeItem<Item>> selectedItems = fileSystemView.getSelectionModel().getSelectedItems(); selectedItems.stream() .map(e -> e.getValue()) .map(e -> e.getPath()) .filter(path -> { if (selectedItems.size() == 1) return true; return !Files.isDirectory(path); }) .forEach(tabService::previewDocument); }); deletePathItem.setOnAction(this::deleteSelectedItems); openFolderTreeItem.setOnAction(event -> { Path path = tabService.getSelectedTabPath(); path = Files.isDirectory(path) ? path : path.getParent(); if (Objects.nonNull(path)) { openInDesktop(path); } }); openFolderListItem.setOnAction(event -> { Path path = recentListView.getSelectionModel().getSelectedItem().getPath(); path = Files.isDirectory(path) ? path : path.getParent(); if (Objects.nonNull(path)) openInDesktop(path); }); openFileListItem.setOnAction(this::openRecentListFile); copyPathListItem.setOnAction(event -> { this.cutCopy(recentListView.getSelectionModel().getSelectedItem().getPath().toString()); }); copyTreeItem.setOnAction(event -> { this.copyFiles(tabService.getSelectedTabPaths()); }); copyListItem.setOnAction(event -> { this.copyFiles(recentListView.getSelectionModel() .getSelectedItems().stream() .map(e -> e.getPath()).collect(Collectors.toList())); }); fileSystemView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); fileSystemView.setOnMouseClicked(event -> { TreeItem<Item> selectedItem = fileSystemView.getSelectionModel().getSelectedItem(); if (Objects.isNull(selectedItem)) return; event.consume(); Path selectedPath = selectedItem.getValue().getPath(); if (event.getButton() == MouseButton.PRIMARY) { if (event.getClickCount() >= 2) { tabService.previewDocument(selectedPath); } } }); fileSystemView.getSelectionModel().getSelectedIndices().addListener((ListChangeListener<? super Integer>) p -> { ObservableList<TreeItem<Item>> selectedItems = fileSystemView.getSelectionModel().getSelectedItems(); if (Objects.isNull(selectedItems)) return; if (selectedItems.size() > 1) { renameFile.setVisible(false); newMenu.setVisible(false); addToFavoriteDir.setVisible(false); renameSeparator.setVisible(false); if (favoriteDirMenu.getItems().size() > 0) { addToFavSeparator.setVisible(true); } else { addToFavSeparator.setVisible(false); } } else if (selectedItems.size() == 1) { TreeItem<Item> itemTreeItem = selectedItems.get(0); Item value = itemTreeItem.getValue(); Path path = value.getPath(); Optional<Path> optional = Optional.ofNullable(selectedItems) .filter(e -> e.size() > 0) .map(e -> e.get(0).getValue()) .map(Item::getPath); if (!optional.isPresent()) { return; } boolean isDirectory = Files.isDirectory(path); newMenu.setVisible(isDirectory); renameFile.setVisible(!isDirectory); renameSeparator.setVisible(true); addToFavoriteDir.setVisible(isDirectory); if (favoriteDirMenu.getItems().size() == 0 && !isDirectory) { addToFavSeparator.setVisible(false); } else { addToFavSeparator.setVisible(true); } ObservableList<String> favoriteDirectories = storedConfigBean.getFavoriteDirectories(); if (favoriteDirectories.size() > 0) { boolean has = favoriteDirectories.contains(path.toString()); if (has) addToFavoriteDir.setDisable(true); else addToFavoriteDir.setDisable(false); } } }); tabService.initializeTabChangeListener(tabPane); this.checkNewVersion(); } private void checkDuplicatedJars() { threadService.runTaskLater(() -> { try { Path lib = getInstallationPath().resolve("lib"); // Path lib = Paths.get("C:\\Program Files\\AsciidocFX\\lib"); if (Files.notExists(lib)) { return; } Map<String, List<Path>> dependencies = IOHelper.list(lib) .collect(Collectors.groupingBy(path -> { Path fileName = path.getFileName(); String name = fileName.toString(); LinkedList<String> nameParts = new LinkedList<String>(Arrays.asList(name.split("-"))); // String lastPart = nameParts.get(nameParts.size() - 1); // String version = lastPart.replaceAll("\\p{Alpha}", ""); nameParts.removeLast(); return String.join("-", nameParts); })); List<String> duplicatePaths = dependencies .entrySet() .stream() .filter(e -> e.getValue().size() > 1) .map(e -> e.getValue()) .map(e -> { return String.join("\n", e.stream().map(Path::toString).collect(Collectors.toList())) + "\n"; }) .collect(Collectors.toList()); if (duplicatePaths.size() > 0) { threadService.runActionLater(() -> { AlertHelper.showDuplicateWarning(duplicatePaths, lib); }); } } catch (Exception e) { } }); } private void deleteSelectedItems(Event event) { List<TreeItem<Item>> selectedItems = fileSystemView .getSelectionModel() .getSelectedItems() .stream() .collect(Collectors.toList()); List<Path> pathList = selectedItems.stream() .map(e -> e.getValue()) .map(e -> e.getPath()) .collect(Collectors.toList()); AlertHelper.deleteAlert(pathList).ifPresent(btn -> { if (btn == ButtonType.YES) { fileSystemView.setDisable(true); threadService.runTaskLater(() -> { try { boolean hasDirectory = false; for (TreeItem<Item> selectedItem : selectedItems) { // int selectedIndex = fileBrowser.findIndex(selectedItem); Path path = selectedItem.getValue().getPath(); if (Files.isDirectory(path)) { if (!hasDirectory) { hasDirectory = true; fileWatchService.unRegisterAllPath(); } if (path.getRoot().equals(path)) { threadService.runActionLater(() -> { AlertHelper.okayAlert("You can't delete fileystem root"); }); continue; } IOHelper.deleteDirectory(path); } else { IOHelper.deleteIfExists(path); } // fileSystemView.getSelectionModel().select(selectedIndex); } if (hasDirectory) { fileBrowser.refresh(); } } catch (Exception ex) { logger.error(ex.getMessage(), ex); } threadService.runActionLater(() -> { fileSystemView.setDisable(false); fileSystemView.requestFocus(); }); }); } }); } public void openInDesktop(Path path) { try { hostServices.showDocument(path.toUri().toString()); } catch (Exception e) { logger.error(e.getMessage(), e); } } public void browseInDesktop(String url) { try { hostServices.showDocument(UriComponentsBuilder.fromUriString(url).build().toUri().toASCIIString()); } catch (Exception e) { logger.error(e.getMessage(), e); } } private ScheduledFuture<?> scheduledFuture = null; private void initializeTerminal() { terminalTabPane.getTabs().addListener(new ListChangeListener<Tab>() { @Override public void onChanged(Change<? extends Tab> c) { while (c.next()) { recordTerminalButton.setDisable(c.getList().isEmpty()); } } }); recordTerminalButton.setOnAction(e -> { if (Objects.isNull(scheduledFuture)) { Tooltip.install(recordTerminalButton, new Tooltip("Stop")); recordTerminalButton.setGraphic(new FontIcon(FontAwesome.STOP_CIRCLE_O)); scheduledFuture = this.recordTerminal(e); } else { Tooltip.install(recordTerminalButton, new Tooltip("Record")); recordTerminalButton.setGraphic(new FontIcon(FontAwesome.PLAY_CIRCLE)); scheduledFuture.cancel(false); scheduledFuture = null; } }); newTerminalButton.setOnAction(this::newTerminal); closeTerminalButton.setOnAction(this::closeTerminal); } private ScheduledFuture<?> recordTerminal(ActionEvent actionEvent) { Tab tab = terminalTabPane.getSelectionModel().getSelectedItem(); if (Objects.isNull(tab)) { return null; } GifExporterFX gifExporterFX = applicationContext.getBean(GifExporterFX.class); try { String gifName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("'Image'-ddMMyy-hhmmss.SSS'.gif'")); ScheduledFuture<?> scheduledFuture = gifExporterFX .captureNow(tab.getContent(), directoryService.workingDirectory().resolve(gifName), 120, true); return scheduledFuture; } catch (Exception e) { e.printStackTrace(); } return null; } TerminalBuilder terminalBuilder = new TerminalBuilder(); @FXML public void newTerminal(ActionEvent actionEvent, Path... path) { if (!terminalToggleButton.isSelected()) { terminalToggleButton.fire(); } TerminalConfig terminalConfig = terminalConfigBean.createTerminalConfig(); terminalBuilder.setTerminalConfig(terminalConfig); Path terminalPath = Optional.ofNullable(path).filter(e -> e.length > 0).map(e -> e[0]).orElse(directoryService.workingDirectory()); terminalBuilder.setTerminalPath(terminalPath); TerminalTab terminalTab = terminalBuilder.newTerminal(); threadService.runActionLater(() -> { terminalTabPane.getTabs().add(terminalTab); terminalTabPane.getSelectionModel().select(terminalTab); }, true); } @FXML public void closeTerminal(ActionEvent actionEvent) { TerminalTab shellTab = (TerminalTab) terminalTabPane.getSelectionModel().getSelectedItem(); Optional.ofNullable(shellTab).ifPresent(TerminalTab::closeTerminal); } public void closeAllTerminal(ActionEvent actionEvent) { ObservableList<Tab> tabs = FXCollections.observableArrayList(terminalTabPane.getTabs()); for (Tab tab : tabs) { ((TerminalTab) tab).closeTerminal(); } } public void closeOtherTerminals(ActionEvent actionEvent) { ObservableList<Tab> tabs = FXCollections.observableArrayList(terminalTabPane.getTabs()); tabs.remove(terminalTabPane.getSelectionModel().getSelectedItem()); for (Tab tab : tabs) { ((TerminalTab) tab).closeTerminal(); } } private void initializeNashornConverter() { nashornEngineConverter.initialize(); } public boolean getStopRendering() { return stopRendering.get(); } public BooleanProperty stopRenderingProperty() { return stopRendering; } public void saveAllTabs() { tabPane.getTabs() .stream() .filter(t -> t instanceof MyTab) .map(t -> (MyTab) t) .filter(t -> !t.isNew()) .forEach(MyTab::saveDoc); } public void loadAllTabs() { tabPane.getTabs() .stream() .filter(t -> t instanceof MyTab) .map(t -> (MyTab) t) .filter(t -> !t.isNew()) .forEach(MyTab::reload); } public void initializeSaveOnBlur() { stage.focusedProperty().addListener((observable, oldValue, newValue) -> { Node focusOwner = stage.getScene().getFocusOwner(); if (StageHelper.getStages().size() == 1) { if (!newValue) { saveAllTabs(); } else { loadAllTabs(); } } if (newValue) { if (Objects.nonNull(focusOwner)) { // logger.info("Focus owner changed {}", focusOwner); focusOwner.requestFocus(); } } }); } public void applyInitialConfigurations() { Double screenX = editorConfigBean.getScreenX(); Double screenY = editorConfigBean.getScreenY(); Double screenWidth = editorConfigBean.getScreenWidth(); Double screenHeight = editorConfigBean.getScreenHeight(); if (Objects.nonNull(screenX)) { stage.setX(screenX); } if (Objects.nonNull(screenY)) { stage.setY(screenY); } if (Objects.nonNull(screenWidth)) { if (screenWidth != 0) { stage.setWidth(screenWidth); } } if (Objects.nonNull(screenHeight)) { if (screenHeight != 0) { stage.setHeight(screenHeight); } } ObservableList<SplitPane.Divider> dividers = splitPane.getDividers(); dividers.get(0).setPosition(editorConfigBean.getFirstSplitter()); dividers.get(1).setPosition(editorConfigBean.getSecondSplitter()); String aceTheme = editorConfigBean.getAceTheme().get(0); editorConfigBean.getEditorTheme().stream().findFirst().ifPresent(theme -> { applyTheme(theme); editorConfigBean.updateAceTheme(aceTheme); }); editorConfigBean.getAceTheme().stream().findFirst().ifPresent(ace -> { applyForAllEditorPanes(editorPane -> editorPane.setTheme(ace)); }); applyForAllEditorPanes(editorPane -> editorPane.setShowGutter(editorConfigBean.getShowGutter())); applyForAllEditorPanes(editorPane -> editorPane.setUseWrapMode(editorConfigBean.getUseWrapMode())); applyForAllEditorPanes(editorPane -> editorPane.setWrapLimitRange(editorConfigBean.getWrapLimit())); applyForAllEditorPanes(editorPane -> editorPane.setFontSize(editorConfigBean.getFontSize())); applyForAllEditorPanes(editorPane -> editorPane.setFoldStyle(editorConfigBean.getFoldStyle())); ObservableList<Item> recentFilesList = storedConfigBean.getRecentFiles(); ObservableList<String> favoriteDirectories = storedConfigBean.getFavoriteDirectories(); recentListView.setCellFactory(param -> { ListCell<Item> cell = new ListCell<Item>() { @Override protected void updateItem(Item item, boolean empty) { super.updateItem(item, empty); if (Objects.nonNull(item)) { setTooltip(new Tooltip(item.getPath().toString())); setText(item.toString()); } } }; return cell; }); recentListView.setItems(recentFilesList); recentFilesList.addListener((ListChangeListener<Item>) c -> { recentListView.visibleProperty().setValue(c.getList().size() > 0); recentListView.getSelectionModel().selectFirst(); }); recentListView.setOnMouseClicked(event -> { if (event.getClickCount() > 1) { openRecentListFile(event); } }); if (favoriteDirectories.size() == 0) { favoriteDirMenu.setVisible(false); } else { int size = 0; for (String favoriteDirectory : favoriteDirectories) { this.addItemToFavoriteDir(size++, favoriteDirectory); } this.includeClearAllToFavoriteDir(); } favoriteDirectories.addListener((ListChangeListener<String>) c -> { c.next(); favoriteDirMenu.setVisible(true); int size = favoriteDirMenu.getItems().size(); boolean empty = size == 0; List<? extends String> addedSubList = c.getAddedSubList(); for (String path : addedSubList) { if (size > 0) this.addItemToFavoriteDir(size++ - 2, path); else this.addItemToFavoriteDir(size++, path); } if (empty) this.includeClearAllToFavoriteDir(); }); // TODO: Lazy initial ? mathJaxService.reload(); String workingDirectory = storedConfigBean.getWorkingDirectory(); if (Objects.nonNull(workingDirectory)) { directoryService.changeWorkigDir(Paths.get(workingDirectory)); } } public void bindConfigurations() { editorConfigBean.getEditorTheme().addListener(new ListChangeListener<EditorConfigBean.Theme>() { @Override public void onChanged(Change<? extends EditorConfigBean.Theme> c) { c.next(); if (c.wasAdded()) { applyTheme(c.getList().get(0)); } } }); editorConfigBean.getAceTheme().addListener(new ListChangeListener<String>() { @Override public void onChanged(Change<? extends String> c) { c.next(); if (c.wasAdded()) { applyForAllEditorPanes(editorPane -> editorPane.setTheme(c.getList().get(0))); } } }); terminalConfigBean.setOnConfigChanged(() -> { applyForEachTerminal(terminalTab -> terminalTab.updatePrefs(terminalConfigBean.createTerminalConfig())); }); locationConfigBean.mathjaxProperty().addListener((observable, oldValue, newValue) -> { if (Objects.nonNull(newValue)) { if (!newValue.equals(oldValue)) { mathJaxService.reload(); } } }); ObservableList<SplitPane.Divider> dividers = splitPane.getDividers(); dividers.get(0).positionProperty().bindBidirectional(editorConfigBean.firstSplitterProperty()); dividers.get(1).positionProperty().bindBidirectional(editorConfigBean.secondSplitterProperty()); SplitPane.Divider verticalDivider = mainVerticalSplitPane.getDividers().get(0); mainVerticalSplitPane.addEventFilter(MouseEvent.MOUSE_RELEASED, event -> { double position = verticalDivider.getPosition(); if (position > 0.1 && position < 0.9) { editorConfigBean.setVerticalSplitter(position); } }); editorConfigBean.showGutterProperty().addListener((observable, oldValue, newValue) -> { if (Objects.nonNull(newValue)) { applyForAllEditorPanes(editorPane -> editorPane.setShowGutter(newValue)); } }); editorConfigBean.useWrapModeProperty().addListener((observable, oldValue, newValue) -> { if (Objects.nonNull(newValue)) { applyForAllEditorPanes(editorPane -> editorPane.setUseWrapMode(newValue)); } }); editorConfigBean.wrapLimitProperty().addListener((observable, oldValue, newValue) -> { if (Objects.nonNull(newValue)) { applyForAllEditorPanes(editorPane -> editorPane.setWrapLimitRange(newValue)); } }); editorConfigBean.fontSizeProperty().addListener((observable, oldValue, newValue) -> { applyForAllEditorPanes(editorPane -> editorPane.setFontSize(newValue.intValue())); }); editorConfigBean.foldStyleProperty().addListener((observable, oldValue, newValue) -> { applyForAllEditorPanes(editorPane -> editorPane.setFoldStyle(newValue)); }); storedConfigBean.workingDirectoryProperty().addListener((observable, oldValue, newValue) -> { if (Objects.nonNull(newValue) && Objects.isNull(oldValue)) { directoryService.changeWorkigDir(Paths.get(newValue)); } }); stage.xProperty().addListener((observable, oldValue, newValue) -> { editorConfigBean.setScreenX(newValue.doubleValue()); }); stage.yProperty().addListener((observable, oldValue, newValue) -> { editorConfigBean.setScreenY(newValue.doubleValue()); }); stage.widthProperty().addListener((observable, oldValue, newValue) -> { editorConfigBean.setScreenWidth(newValue.doubleValue()); }); stage.heightProperty().addListener((observable, oldValue, newValue) -> { editorConfigBean.setScreenHeight(newValue.doubleValue()); }); } private void getImageSizeInfo(String path, Object info) { if (path.startsWith("/")) path = path.substring(1); Path parent = null; try { if (current.currentPath().isPresent()) { parent = current.currentPath().get().getParent(); } else { parent = directoryService.workingDirectory(); } } catch (Exception e) { logger.debug("Problem occured while getting parent path", e); } if (Objects.isNull(parent)) return; Path imagePath = parent.resolve(path); if (Files.notExists(imagePath)) return; try (ImageInputStream in = ImageIO.createImageInputStream(imagePath.toFile())) { final Iterator<ImageReader> readers = ImageIO.getImageReaders(in); if (readers.hasNext()) { ImageReader reader = readers.next(); try { reader.setInput(in); int width = reader.getWidth(0); int height = reader.getHeight(0); if ((info instanceof JSObject)) { JSObject object = (JSObject) info; object.setMember("width", width); object.setMember("height", height); } else if (info instanceof jdk.nashorn.api.scripting.JSObject) { jdk.nashorn.api.scripting.JSObject object = (jdk.nashorn.api.scripting.JSObject) info; object.setMember("width", width); object.setMember("height", height); ; } reader.dispose(); return; } finally { reader.dispose(); } } } catch (Exception e) { logger.error("Problem occured while getting image size info", e); } } @WebkitCall(from = "asciidoctor-image-size-info") public void getImageInfo(final String path, Object info) { if ((info instanceof JSObject)) { threadService.runActionLater(() -> { getImageSizeInfo(path, info); }); } else if (info instanceof jdk.nashorn.api.scripting.JSObject) { getImageSizeInfo(path, info); } } private void applyForAllEditorPanes(Consumer<EditorPane> editorPaneConsumer) { ObservableList<Tab> tabs = tabPane.getTabs(); for (Tab tab : tabs) { if (tab instanceof MyTab) { MyTab myTab = (MyTab) tab; editorPaneConsumer.accept(myTab.getEditorPane()); } } } private void applyForEachTerminal(Consumer<TerminalTab> terminalTabConsumer) { ObservableList<Tab> tabs = terminalTabPane.getTabs(); for (Tab tab : tabs) { terminalTabConsumer.accept(((TerminalTab) tab)); } } private void includeClearAllToFavoriteDir() { favoriteDirMenu.getItems().addAll(new SeparatorMenuItem(), MenuItemBuilt .item("Clear List") .click(event -> { ObservableList<TreeItem<Item>> selectedItems = fileSystemView.getSelectionModel().getSelectedItems(); if (selectedItems.size() == 1) { Path path = selectedItems.get(0).getValue().getPath(); boolean isDirectory = Files.isDirectory(path); addToFavSeparator.setVisible(isDirectory); } else addToFavSeparator.setVisible(false); storedConfigBean.getFavoriteDirectories().clear(); favoriteDirMenu.getItems().clear(); favoriteDirMenu.setVisible(false); addToFavoriteDir.setDisable(false); })); } private void addItemToFavoriteDir(int index, String path) { favoriteDirMenu.getItems().add(index, MenuItemBuilt .item(path) .tip("Go to favorite dir") .click(event -> { directoryService.changeWorkigDir(Paths.get(path)); })); } private void checkNewVersion() { threadService.schedule(() -> { try { if (!editorConfigBean.getAutoUpdate()) return; ApplicationLauncher.launchApplication("504", null, false, new ApplicationLauncher.Callback() { public void exited(int exitValue) { //TODO add your code here (not invoked on event dispatch thread) } public void prepareShutdown() { //TODO add your code here (not invoked on event dispatch thread) } } ); } catch (Exception e) { // logger.error("Problem occured while checking new version", e); } }, 10, TimeUnit.SECONDS); } private void generateODFDocument(boolean askPath) { if (!current.currentPath().isPresent()) this.saveDoc(); threadService.runTaskLater(() -> { odfConverter.generateODFDocument(askPath); }); } private void generateODFDocument() { this.generateODFDocument(false); } private void initializeDoctypes() { try { ObjectMapper mapper = new ObjectMapper(); Object readValue = mapper.readValue(getConfigPath().resolve("ace_doctypes.json").toFile(), new TypeReference<List<DocumentMode>>() { }); modeList.addAll((Collection) readValue); supportedModes = modeList.stream() .map(d -> d.getExtensions()) .filter(Objects::nonNull) .flatMap(d -> Arrays.asList(d.split("\\|")).stream()) .collect(Collectors.toList()); } catch (Exception e) { e.printStackTrace(); logger.error("Problem occured while loading document types", e); } } public Path getInstallationPath() { if (Objects.isNull(installationPath)) { try { String homeProp = System.getProperty("asciidocfx.home"); if (homeProp != null) { installationPath = new File(homeProp).toPath(); } else { //guess installation path CodeSource codeSource = ApplicationController.class.getProtectionDomain().getCodeSource(); File jarFile = new File(codeSource.getLocation().toURI().getPath()); installationPath = jarFile.toPath().getParent().getParent(); } } catch (Exception e) { logger.error("Problem occured while resolving conf and log paths", e); } } return installationPath; } public String getLogPath() { if (Objects.isNull(logPath)) { Optional<String> linuxHome = Optional.ofNullable(System.getenv("HOME")); Optional<String> windowsHome = Optional.ofNullable(System.getenv("USERPROFILE")); Stream.<Optional<String>>of(linuxHome, windowsHome) .filter(Optional::isPresent) .map(Optional::get) .map(Paths::get) .findFirst() .ifPresent(path -> logPath = path.resolve(configDirName).resolve("log").toString()); } return logPath; } @WebkitCall public void updateStatusBox(long row, long column, long linecount, long wordcount) { threadService.runActionLater(() -> { statusText.setText(String.format("(Characters: %d) (Lines: %d) (%d:%d)", wordcount, linecount, row, column)); }); } private void initializeLogViewer() { TableView<MyLog> logViewer = new TableView<>(); logViewer.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); ContextMenu logViewerContextMenu = new ContextMenu(); logViewerContextMenu.getItems().add(MenuItemBuilt.item("Copy").click(e -> { ObservableList<MyLog> rowList = (ObservableList) logViewer.getSelectionModel().getSelectedItems(); StringBuilder clipboardString = new StringBuilder(); for (MyLog rowLog : rowList) { clipboardString.append(String.format("%s => %s", rowLog.getLevel(), rowLog.getMessage())); clipboardString.append("\n\n"); } ClipboardContent clipboardContent = new ClipboardContent(); clipboardContent.putString(clipboardString.toString()); Clipboard.getSystemClipboard().setContent(clipboardContent); })); logViewer.setContextMenu(logViewerContextMenu); logViewer.getStyleClass().add("log-viewer"); FilteredList<MyLog> logFilteredList = new FilteredList<MyLog>(logList, log -> true); logViewer.setItems(logFilteredList); // logViewer.setColumnResizePolicy((param) -> true); logViewer.getItems().addListener((ListChangeListener<MyLog>) c -> { c.next(); final int size = logViewer.getItems().size(); if (size > 0) { logViewer.scrollTo(size - 1); } }); TableColumn<MyLog, String> levelColumn = new TableColumn<>("Level"); levelColumn.getStyleClass().add("level-column"); TableColumn<MyLog, String> messageColumn = new TableColumn<>("Message"); levelColumn.setCellValueFactory(new PropertyValueFactory<MyLog, String>("level")); messageColumn.setCellValueFactory(new PropertyValueFactory<MyLog, String>("message")); messageColumn.prefWidthProperty().bind(logViewer.widthProperty().subtract(levelColumn.widthProperty()).subtract(5)); logViewer.setRowFactory(param -> new TableRow<MyLog>() { @Override protected void updateItem(MyLog item, boolean empty) { super.updateItem(item, empty); if (!empty) { getStyleClass().removeAll("DEBUG", "INFO", "WARN", "ERROR"); getStyleClass().add(item.getLevel()); } } }); messageColumn.setCellFactory(param -> new TableCell<MyLog, String>() { @Override protected void updateItem(String item, boolean empty) { if (item == getItem()) return; super.updateItem(item, empty); if (item == null) { super.setText(null); super.setGraphic(null); } else { Text text = new Text(item); super.setGraphic(text); text.wrappingWidthProperty().bind(messageColumn.widthProperty().subtract(0)); } } }); logViewer.getColumns().addAll(levelColumn, messageColumn); logViewer.setEditable(true); TableViewLogAppender.setThreadService(threadService); TableViewLogAppender.setLogList(logList); TableViewLogAppender.setStatusMessage(statusMessage); TableViewLogAppender.setShowHideLogs(logToggleButton); TableViewLogAppender.setLogViewer(logViewer); final EventHandler<ActionEvent> filterByLogLevel = event -> { ToggleButton logLevelItem = (ToggleButton) event.getTarget(); if (Objects.nonNull(logLevelItem)) { logFilteredList.setPredicate(myLog -> { String text = logLevelItem.getText(); return text.equals("All") || text.equalsIgnoreCase(myLog.getLevel()); }); } }; ToggleGroup toggleGroup = new ToggleGroup(); ToggleButton allToggle = ToggleButtonBuilt.item("All").tip("Show all").click(filterByLogLevel); ToggleButton errorToggle = ToggleButtonBuilt.item("Error").tip("Filter by Error").click(filterByLogLevel); ToggleButton warnToggle = ToggleButtonBuilt.item("Warn").tip("Filter by Warn").click(filterByLogLevel); ToggleButton infoToggle = ToggleButtonBuilt.item("Info").tip("Filter by Info").click(filterByLogLevel); ToggleButton debugToggle = ToggleButtonBuilt.item("Debug").tip("Filter by Debug").click(filterByLogLevel); toggleGroup.getToggles().addAll( allToggle, errorToggle, warnToggle, infoToggle, debugToggle ); toggleGroup.selectToggle(allToggle); Button clearLogsButton = new Button("Clear"); clearLogsButton.setOnAction(e -> { statusMessage.setText(""); logList.clear(); }); Button browseLogsButton = new Button("Browse"); browseLogsButton.setOnAction(e -> { openInDesktop(Paths.get(logPath)); }); TextField searchLogField = new TextField(); searchLogField.setPromptText("Search in logs.."); searchLogField.textProperty().addListener((observable, oldValue, newValue) -> { if (Objects.isNull(newValue)) { return; } if (newValue.isEmpty()) { logFilteredList.setPredicate(myLog -> true); } logFilteredList.setPredicate(myLog -> { final AtomicBoolean result = new AtomicBoolean(false); String message = myLog.getMessage(); if (Objects.nonNull(message)) { if (!result.get()) result.set(message.toLowerCase().contains(newValue.toLowerCase())); } String level = myLog.getLevel(); String toggleText = ((ToggleButton) toggleGroup.getSelectedToggle()).getText(); boolean inputContains = level.toLowerCase().contains(newValue.toLowerCase()); if (Objects.nonNull(level)) { if (!result.get()) { result.set(inputContains); } } boolean levelContains = toggleText.toLowerCase().equalsIgnoreCase(level); if (!toggleText.equals("All") && !levelContains) { result.set(false); } return result.get(); }); }); List<Control> controls = Arrays.asList(allToggle, errorToggle, warnToggle, infoToggle, debugToggle, searchLogField, clearLogsButton, browseLogsButton); FlowPane logFlowPane = new FlowPane(5, 5); for (Control control : controls) { logFlowPane.getChildren().add(control); control.prefHeightProperty().bind(searchLogField.heightProperty()); } logViewer.setMinHeight(0); logVBox.setMinHeight(0); logVBox.getChildren().addAll(logFlowPane, logViewer); VBox.setVgrow(logViewer, Priority.ALWAYS); } private void openRecentListFile(Event event) { tabService.previewDocument(recentListView.getSelectionModel().getSelectedItem().getPath()); } public void externalBrowse() { rightShowerHider.getShowing().ifPresent(ViewPanel::browse); } ChangeListener<Boolean> outlineTabChangeListener; @WebkitCall(from = "index") public void fillOutlines(Object doc) { if (outlineTreeView.isVisible()) { converterProvider.get(previewConfigBean).fillOutlines(doc); } if (Objects.nonNull(outlineTabChangeListener)) { outlineTreeView.visibleProperty().removeListener(outlineTabChangeListener); } outlineTabChangeListener = (observable, oldValue, newValue) -> { if (newValue) { converterProvider.get(previewConfigBean).fillOutlines(doc); } }; outlineTreeView.visibleProperty().addListener(outlineTabChangeListener); } @WebkitCall(from = "index") public void clearOutline() { outlineList = new TreeSet<>(); } @WebkitCall(from = "index") public void finishOutline() { threadService.runActionLater(() -> { if (outlineTreeView.getRoot() == null) { TreeItem<Section> rootItem = new TreeItem<>(); rootItem.setExpanded(true); Section rootSection = new Section(); rootSection.setLevel(-1); String outlineTitle = "Outline"; rootSection.setTitle(outlineTitle); rootItem.setValue(rootSection); outlineTreeView.setRoot(rootItem); outlineTreeView.setOnMouseClicked(event -> { try { TreeItem<Section> item = outlineTreeView.getSelectionModel().getSelectedItem(); EditorPane editorPane = current.currentEditor(); editorPane.moveCursorTo(item.getValue().getLineno()); } catch (Exception e) { logger.error("Problem occured while jumping from outline"); } }); } if (outlineList.size() > 0) outlineTreeView.getRoot().getChildren().clear(); for (Section section : outlineList) { TreeItem<Section> sectionItem = new TreeItem<>(section); sectionItem.setExpanded(true); outlineTreeView.getRoot().getChildren().add(sectionItem); TreeSet<Section> subsections = section.getSubsections(); for (Section subsection : subsections) { TreeItem<Section> subItem = new TreeItem<>(subsection); subItem.setExpanded(true); sectionItem.getChildren().add(subItem); this.addSubSections(subItem, subsection.getSubsections()); } } }); } private void addSubSections(TreeItem<Section> subItem, TreeSet<Section> outlineList) { for (Section section : outlineList) { TreeItem<Section> sectionItem = new TreeItem<>(section); subItem.getChildren().add(sectionItem); TreeSet<Section> subsections = section.getSubsections(); for (Section subsection : subsections) { TreeItem<Section> item = new TreeItem<>(subsection); sectionItem.getChildren().add(item); this.addSubSections(item, subsection.getSubsections()); } } } @WebkitCall(from = "index") public void fillOutline(String parentLineNo, String level, String title, String lineno, String id) { Section section = new Section(); section.setLevel(Integer.valueOf(level)); section.setTitle(title); section.setLineno(Integer.valueOf(lineno)); section.setId(id); if (Objects.isNull(parentLineNo)) outlineList.add(section); else { Integer parentLine = Integer.valueOf(parentLineNo); Optional<Section> parentSection = outlineList.stream() .filter(e -> e.getLineno().equals(parentLine)) .findFirst(); if (parentSection.isPresent()) parentSection.get().getSubsections().add(section); else { this.traverseEachSubSection(outlineList, parentLine, section); } } } private void traverseEachSubSection(TreeSet<Section> sections, Integer parentLine, Section section) { sections.stream().forEach(s -> { Optional<Section> subs = s.getSubsections().stream() .filter(e -> e.getLineno().equals(parentLine)) .findFirst(); if (subs.isPresent()) subs.get().getSubsections().add(section); else this.traverseEachSubSection(s.getSubsections(), parentLine, section); }); } @FXML public void changeWorkingDir(Event actionEvent) { directoryService.askWorkingDir(); } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessionList.add(session); Optional .ofNullable(lastConverterResult) .map(ConverterResult::getRendered) .ifPresent(this::sendOverWebSocket); } @FXML public void closeApp(ActionEvent event) { try { Map<String, ConfigurationBase> configurationBeansAsMap = applicationContext.getBeansOfType(ConfigurationBase.class); for (ConfigurationBase configurationBean : configurationBeansAsMap.values()) { configurationBean.save(event); } } catch (Exception e) { logger.error("Error while closing app", e); } } @FXML public void openDoc(Event... event) { tabService.openDoc(); } @FXML public void newDoc(Event... event) { threadService.runActionLater(() -> { tabService.newDoc(); }, true); } @WebkitCall(from = "editor") public boolean isLiveReloadPane() { return liveReloadPane.isVisible(); } @WebkitCall(from = "editor") public void onscroll(Object pos, Object max) { rightShowerHider.getShowing() .ifPresent(s -> s.onscroll(pos, max)); } @WebkitCall(from = "editor") public void scrollByLine(String text) { threadService.runActionLater(() -> { try { rightShowerHider.getShowing().ifPresent(w -> w.scrollByLine(text)); } catch (Exception e) { logger.debug(e.getMessage(), e); } }); } @WebkitCall(from = "click-binder") public void moveCursorTo(int line) { current.currentEditor().moveCursorTo(line); } @WebkitCall(from = "editor") public void scrollByPosition(String text) { threadService.runActionLater(() -> { try { String selection = asciidocWebkitConverter.findRenderedSelection(text); rightShowerHider.getShowing().ifPresent(w -> w.scrollByPosition(selection)); } catch (Exception e) { logger.debug(e.getMessage(), e); } }); } @WebkitCall(from = "asciidoctor-uml") public void uml(String uml, String type, String imagesDir, String imageTarget, String nodename) throws IOException { plantuml(uml, type, imagesDir, imageTarget, nodename); } @WebkitCall(from = "asciidoctor-uml") public void plantuml(String uml, String type, String imagesDir, String imageTarget, String nodename) throws IOException { threadService.runTaskLater(() -> { plantUmlService.plantUml(uml, type, imagesDir, imageTarget, nodename); }); } @WebkitCall(from = "asciidoctor-uml") public void graphviz(String graphviz, String type, String imagesDir, String imageTarget, String nodename) throws IOException { this.plantuml(graphviz, type, imagesDir, imageTarget, nodename); } @WebkitCall(from = "asciidoctor-uml") public void ditaa(String ditaa, String type, String imagesDir, String imageTarget, String nodename) throws IOException { this.plantuml(ditaa, type, imagesDir, imageTarget, nodename); } @WebkitCall(from = "asciidoctor-chart") public void chartBuildFromCsv(String csvFile, String imagesDir, String imageTarget, String chartType, String options) { threadService.runActionLater(() -> { if (Objects.isNull(imageTarget) || Objects.isNull(chartType)) return; current.currentPath().map(Path::getParent).ifPresent(root -> { threadService.runTaskLater(() -> { String csvContent = IOHelper.readFile(root.resolve(csvFile)); threadService.runActionLater(() -> { try { Map<String, String> optMap = parseChartOptions(options); optMap.put("csv-file", csvFile); chartProvider.getProvider(chartType).chartBuild(csvContent, imagesDir, imageTarget, optMap); } catch (Exception e) { logger.info(e.getMessage(), e); } }); }); }); }); } @WebkitCall(from = "asciidoctor-chart") public void chartBuild(String chartContent, String imagesDir, String imageTarget, String chartType, String options) { if (Objects.isNull(imageTarget) || Objects.isNull(chartType)) return; threadService.runActionLater(() -> { try { Map<String, String> optMap = parseChartOptions(options); chartProvider.getProvider(chartType).chartBuild(chartContent, imagesDir, imageTarget, optMap); } catch (Exception e) { logger.info(e.getMessage(), e); } }); } private Map<String, String> parseChartOptions(String options) { Map<String, String> optMap = new HashMap<>(); if (Objects.nonNull(options)) { String[] optPart = options.split(","); for (String opt : optPart) { String[] keyVal = opt.split("="); if (keyVal.length != 2) continue; optMap.put(keyVal[0], keyVal[1]); } } return optMap; } @WebkitCall(from = "converter.js") public void completeWebWorkerExceptionally(Object taskId) { threadService.runTaskLater(() -> { final Map<String, CompletableFuture<ConverterResult>> workerTasks = asciidocWebkitConverter.getWebWorkerTasks(); Optional.ofNullable(workerTasks.get(taskId)) .filter(c -> !c.isDone()) .ifPresent(c -> { final RuntimeException ex = new RuntimeException(String.format("Task: %s is not completed", taskId)); c.completeExceptionally(ex); }); workerTasks.remove(taskId); }); } @WebkitCall(from = "converter.js") public void completeWebWorker(String taskId, String rendered, String backend, String doctype) { threadService.runTaskLater(() -> { final ConverterResult converterResult = new ConverterResult(taskId, rendered, backend, doctype); final Map<String, CompletableFuture<ConverterResult>> workerTasks = asciidocWebkitConverter.getWebWorkerTasks(); final CompletableFuture<ConverterResult> completableFuture = workerTasks.get(converterResult.getTaskId()); Optional.ofNullable(completableFuture) .filter(c -> !c.isDone()) .ifPresent(c -> { c.complete(converterResult); }); workerTasks.remove(converterResult.getTaskId()); }); } private AtomicReference<Tuple<String, String>> latestTupleReference = new AtomicReference<>(); private Semaphore renderLoopSemaphore = new Semaphore(1); private void renderLoop() throws InterruptedException { renderLoopSemaphore.acquire(); if (stopRendering.get()) { return; } Tuple<String, String> tuple = latestTupleReference.get(); if (Objects.isNull(tuple)) return; String text = tuple.getKey(); String mode = tuple.getValue(); try { boolean bookArticleHeader = this.bookArticleHeaderRegex.matcher(text).find(); boolean forceInclude = this.forceIncludeRegex.matcher(text).find(); if ("asciidoc".equalsIgnoreCase(mode)) { if (bookArticleHeader && !forceInclude) setIncludeAsciidocResource(true); this.lastConverterResult = converterProvider.get(previewConfigBean).convertAsciidoc(text); setIncludeAsciidocResource(false); if (lastConverterResult.isBackend("html5")) { updateRendered(lastConverterResult.getRendered()); rightShowerHider.showNode(htmlPane); } if (lastConverterResult.isBackend("revealjs") || lastConverterResult.isBackend("deckjs")) { slidePane.setBackend(lastConverterResult.getBackend()); slideConverter.convert(lastConverterResult.getRendered()); rightShowerHider.showNode(slidePane); } } else if ("html".equalsIgnoreCase(mode)) { // if (liveReloadPane.getReady()) { // liveReloadPane.updateDomdom(); // } else { threadService.buff("htmlEditor") .schedule(() -> { liveReloadPane.load(String.format(liveUrl, port, directoryService.interPath())); }, 500, TimeUnit.MILLISECONDS); // } rightShowerHider.showNode(liveReloadPane); } else if ("markdown".equalsIgnoreCase(mode)) { MarkdownService markdownService = applicationContext.getBean(MarkdownService.class); markdownService.convertToAsciidoc(text, asciidoc -> { ConverterResult result = converterProvider.get(previewConfigBean).convertAsciidoc(asciidoc); result.afterRender(this::updateRendered); }); rightShowerHider.showNode(htmlPane); } } catch (Exception e) { setIncludeAsciidocResource(false); logger.error("Problem occured while rendering content", e); } } private void updateRendered(String rendered) { Optional.ofNullable(rendered) .ifPresent(html -> { htmlPane.refreshUI(html); sendOverWebSocket(html); }); } private void sendOverWebSocket(String html) { if (sessionList.size() > 0) { threadService.runTaskLater(() -> { sessionList.stream().filter(WebSocketSession::isOpen).forEach(e -> { try { e.sendMessage(new TextMessage(html)); } catch (Exception ex) { logger.error("Problem occured while sending content over WebSocket", ex); } }); }); } } @WebkitCall(from = "editor") public void textListener(String text, String mode) { latestTupleReference.set(new Tuple<>(text, mode)); if (renderLoopSemaphore.hasQueuedThreads()) { renderLoopSemaphore.release(); } } @WebkitCall(from = "editor") public void checkWordSuggestions(String word) { final List<String> stringList = dictionaryService.getSuggestionMap() .getOrDefault(word, Arrays.asList()); current.currentEditor().showSuggestions(stringList); } @WebkitCall(from = "editor") public void processTokens() { if (spellcheckConfigBean.getDisableSpellCheck()) { return; } final EditorPane editorPane = current.currentEditor(); final String tokenList = editorPane.tokenList(); final String mode = editorPane.editorMode(); threadService.runTaskLater(() -> { dictionaryService.processTokens(editorPane, tokenList, mode); }); } @WebkitCall(from = "asciidoctor-odf.js") public synchronized void convertToOdf(String name, Object obj) throws Exception { JSObject jObj = (JSObject) obj; odfConverter.buildDocument(name, jObj); } @WebkitCall public String getTemplate(String templateDir) { return asciidocWebkitConverter.getTemplate(templateDir); } public void cutCopy(String data) { ClipboardContent clipboardContent = new ClipboardContent(); clipboardContent.putString(data.replaceAll("\\R", "\n")); Clipboard.getSystemClipboard().setContent(clipboardContent); } public void copyFiles(List<Path> paths) { Optional.ofNullable(paths) .filter((ps) -> !ps.isEmpty()) .ifPresent(ps -> { ClipboardContent clipboardContent = new ClipboardContent(); clipboardContent.putFiles(ps .stream() .map(Path::toFile) .collect(Collectors.toList())); Clipboard.getSystemClipboard().setContent(clipboardContent); }); } @WebkitCall(from = "asciidoctor") public String readDefaultStylesheet() { Optional<Path> optional = Optional.ofNullable(locationConfigBean.getStylesheetDefault()) .filter((s) -> !s.isEmpty()) .map(Paths::get) .filter(Files::exists); Path path = optional.orElse(getConfigPath().resolve("public/css/asciidoctor-default.css")); return IOHelper.readFile(path); } @WebkitCall(from = "asciidoctor") public String readAsciidoctorResource(String uri, Integer parent) { if (uri.matches(".*?\\.(asc|adoc|ad|asciidoc|md|markdown)") && getIncludeAsciidocResource()) return String.format("link:%s[]", uri); PathFinderService fileReader = applicationContext.getBean("pathFinder", PathFinderService.class); Path path = fileReader.findPath(uri, parent); if (!Files.exists(path)) { return "404"; } else { return IOHelper.readFile(path); } } @WebkitCall public String clipboardValue() { return Clipboard.getSystemClipboard().getString(); } @WebkitCall public void pasteRaw() { EditorPane editorPane = current.currentEditor(); Clipboard systemClipboard = Clipboard.getSystemClipboard(); if (systemClipboard.hasFiles()) { Optional<String> block = parserService.toImageBlock(systemClipboard.getFiles()); if (block.isPresent()) { editorPane.insert(block.get()); return; } } if (systemClipboard.hasImage()) { Image image = systemClipboard.getImage(); Optional<String> block = parserService.toImageBlock(image); if (block.isPresent()) { editorPane.insert(block.get()); return; } } editorPane.execCommand("paste-raw-1"); } @WebkitCall public void paste() { EditorPane editorPane = current.currentEditor(); Clipboard systemClipboard = Clipboard.getSystemClipboard(); if (systemClipboard.hasFiles()) { Optional<String> block = parserService.toImageBlock(systemClipboard.getFiles()); if (block.isPresent()) { editorPane.insert(block.get()); return; } } if (systemClipboard.hasImage()) { Image image = systemClipboard.getImage(); Optional<String> block = parserService.toImageBlock(image); if (block.isPresent()) { editorPane.insert(block.get()); return; } } try { if (systemClipboard.hasHtml() || asciidocWebkitConverter.isHtml(systemClipboard.getString())) { String content = Optional.ofNullable(systemClipboard.getHtml()).orElse(systemClipboard.getString()); if (current.currentTab().isAsciidoc() || current.currentTab().isMarkdown()) content = (String) asciidocWebkitConverter.call(current.currentTab().htmlToMarkupFunction(), content); editorPane.insert(content); return; } } catch (Exception e) { logger.error(e.getMessage(), e); } editorPane.execCommand("paste-raw-1"); } public void adjustSplitPane() { final Toggle selectedToggle1 = leftToggleGroup.getSelectedToggle(); final Toggle selectedToggle2 = rightToggleGroup.getSelectedToggle(); if (Objects.nonNull(selectedToggle1)) { ((ToggleButton) selectedToggle1).fire(); } if (Objects.nonNull(selectedToggle2)) { ((ToggleButton) selectedToggle2).fire(); } if (Objects.isNull(selectedToggle1) && Objects.isNull(selectedToggle2)) { ((ToggleButton) leftToggleGroup.getToggles().get(0)).fire(); ((ToggleButton) rightToggleGroup.getToggles().get(0)).fire(); } } public void saveDoc() { current.currentTab().saveDoc(); } @FXML public void saveDoc(Event actionEvent) { current.currentTab().saveDoc(); } public void fitToParent(Node node) { AnchorPane.setTopAnchor(node, 0.0); AnchorPane.setBottomAnchor(node, 0.0); AnchorPane.setLeftAnchor(node, 0.0); AnchorPane.setRightAnchor(node, 0.0); } public void saveAndCloseCurrentTab() { // this.saveDoc(); threadService.runActionLater(current.currentTab()::close); } public ProgressIndicator getIndikator() { return indikator; } public void setStage(Stage stage) { this.stage = stage; } public Stage getStage() { return stage; } public Scene getScene() { return scene; } public void setScene(Scene scene) { this.scene = scene; } public void setAsciidocTableAnchor(AnchorPane asciidocTableAnchor) { this.asciidocTableAnchor = asciidocTableAnchor; } public AnchorPane getAsciidocTableAnchor() { return asciidocTableAnchor; } public void setAsciidocTableStage(Stage asciidocTableStage) { this.asciidocTableStage = asciidocTableStage; } public Stage getAsciidocTableStage() { return asciidocTableStage; } public SplitPane getSplitPane() { return splitPane; } public TreeView<Item> getFileSystemView() { return fileSystemView; } public AsciidocTableController getAsciidocTableController() { return asciidocTableController; } public TabPane getTabPane() { return tabPane; } public AnchorPane getRootAnchor() { return rootAnchor; } public int getPort() { return port; } public Path getConfigPath() { if (Objects.isNull(configPath)) { configPath = getInstallationPath().resolve("conf"); } return configPath; } public Current getCurrent() { return current; } @FXML private void bugReport(ActionEvent actionEvent) { browseInDesktop(issuePage); } @FXML private void openCommunityForum(ActionEvent actionEvent) { browseInDesktop(issueForum); } @FXML private void openGitterChat(ActionEvent actionEvent) { browseInDesktop(gitterChat); } @FXML private void openGithubPage(ActionEvent actionEvent) { browseInDesktop(githubPage); } @FXML public void generateCheatSheet(ActionEvent actionEvent) { Path cheatsheetPath = getConfigPath().resolve("cheatsheet/cheatsheet.adoc"); Path tempSheetPath = IOHelper.createTempDirectory(directoryService.workingDirectory(), "cheatsheet") .resolve("cheatsheet.adoc"); IOHelper.copy(cheatsheetPath, tempSheetPath); tabService.addTab(tempSheetPath); } public void setMarkdownTableAnchor(AnchorPane markdownTableAnchor) { this.markdownTableAnchor = markdownTableAnchor; } public AnchorPane getMarkdownTableAnchor() { return markdownTableAnchor; } public void setMarkdownTableStage(Stage markdownTableStage) { this.markdownTableStage = markdownTableStage; } public Stage getMarkdownTableStage() { return markdownTableStage; } public ShortcutProvider getShortcutProvider() { return shortcutProvider; } @WebkitCall public void log(Object object) { debug(object); } @WebkitCall public void debug(Object object) { logger.debug(object + ""); } @WebkitCall public void warn(Object object) { logger.warn(object + ""); } @WebkitCall public void info(Object object) { logger.info(object + ""); } @WebkitCall public void error(Object object) { logger.error(object + ""); } @FXML public void createFolder(ActionEvent actionEvent) { DialogBuilder dialog = DialogBuilder.newFolderDialog(); Consumer<String> consumer = result -> { if (dialog.isShowing()) dialog.hide(); if (result.matches(DialogBuilder.FOLDER_NAME_REGEX)) { Path path = fileSystemView.getSelectionModel().getSelectedItem() .getValue().getPath(); Path folderPath = path.resolve(result); threadService.runTaskLater(() -> { IOHelper.createDirectories(folderPath); // threadService.runActionLater(() -> { // directoryService.changeWorkigDir(folderPath); // }); }); } }; dialog.getEditor().setOnAction(event -> { consumer.accept(dialog.getEditor().getText().trim()); }); dialog.showAndWait().map(String::trim).ifPresent(consumer); } @FXML public void createFile(ActionEvent actionEvent) { DialogBuilder dialog = DialogBuilder.newFileDialog(); Consumer<String> consumer = result -> { if (dialog.isShowing()) dialog.hide(); if (result.matches(DialogBuilder.FILE_NAME_REGEX)) { Path path = fileSystemView.getSelectionModel().getSelectedItem() .getValue().getPath(); IOHelper.createDirectories(path); Optional<Exception> exception = IOHelper.writeToFile(path.resolve(result), "", CREATE_NEW, WRITE); if (!exception.isPresent()) { tabService.addTab(path.resolve(result)); } } }; dialog.getEditor().setOnAction(event -> { consumer.accept(dialog.getEditor().getText().trim()); }); dialog.showAndWait().ifPresent(consumer); } @FXML public void renameFile(Event actionEvent) { RenameDialog dialog = RenameDialog.create(); Path path = fileSystemView.getSelectionModel().getSelectedItem() .getValue().getPath(); TextField editor = dialog.getEditor(); editor.setText(path.getFileName().toString()); Consumer<String> consumer = result -> { if (dialog.isShowing()) dialog.hide(); threadService.runTaskLater(() -> { if (result.trim().matches("^[^\\\\/:?*\"<>|]+$")) { IOHelper.move(path, path.getParent().resolve(result.trim())); } }); }; editor.setOnAction(event -> { consumer.accept(editor.getText()); }); if (!Files.isDirectory(path)) { int extensionIndex = editor.getText().lastIndexOf("."); if (extensionIndex > 0) { threadService.runActionLater(() -> { editor.selectRange(0, extensionIndex); }, true); } } dialog.showAndWait().ifPresent(consumer); } @FXML public void gitbookToAsciibook(ActionEvent actionEvent) { File gitbookRoot = null; File asciibookRoot = null; BiPredicate<File, File> nullPathPredicate = (p1, p2) -> Objects.isNull(p1) || Objects.isNull(p2); DirectoryChooser gitbookChooser = new DirectoryChooser(); gitbookChooser.setTitle("Select Gitbook Root Directory"); gitbookRoot = gitbookChooser.showDialog(null); DirectoryChooser asciibookChooser = new DirectoryChooser(); asciibookChooser.setTitle("Select Blank Asciibook Root Directory"); asciibookRoot = asciibookChooser.showDialog(null); if (nullPathPredicate.test(gitbookRoot, asciibookRoot)) { AlertHelper.nullDirectoryAlert(); return; } final File finalGitbookRoot = gitbookRoot; final File finalAsciibookRoot = asciibookRoot; threadService.runTaskLater(() -> { logger.debug("Gitbook to Asciibook conversion started"); indikatorService.startProgressBar(); GitbookToAsciibookService toAsciiBook = applicationContext.getBean(GitbookToAsciibookService.class); toAsciiBook.gitbookToAsciibook(finalGitbookRoot.toPath(), finalAsciibookRoot.toPath()); indikatorService.stopProgressBar(); logger.debug("Gitbook to Asciibook conversion ended"); }); } /* @WebkitCall public void fillModeList(String mode) { threadService.runActionLater(() -> { modeList.add(mode); }); }*/ public void clearImageCache(Path imagePath) { rightShowerHider.getShowing() .ifPresent(w -> w.clearImageCache(imagePath)); } public void clearImageCache(String imagePath) { rightShowerHider.getShowing() .ifPresent(w -> w.clearImageCache(imagePath)); } public ObservableList<DocumentMode> getModeList() { return modeList; } public List<String> getSupportedModes() { return supportedModes; } public boolean getIncludeAsciidocResource() { return includeAsciidocResource.get(); } public void setIncludeAsciidocResource(boolean includeAsciidocResource) { this.includeAsciidocResource.set(includeAsciidocResource); } public ProgressBar getProgressBar() { return progressBar; } public Timeline getProgressBarTimeline() { return progressBarTimeline; } @FXML public void newSlide(ActionEvent actionEvent) { DialogBuilder dialog = DialogBuilder.newFolderDialog(); dialog.showAndWait().map(String::trim).ifPresent(folderName -> { if (dialog.isShowing()) dialog.hide(); if (folderName.matches(DialogBuilder.FOLDER_NAME_REGEX)) { Path path = fileSystemView.getSelectionModel().getSelectedItem() .getValue().getPath(); Path folderPath = path.resolve(folderName); threadService.runTaskLater(() -> { IOHelper.createDirectories(folderPath); indikatorService.startProgressBar(); IOHelper.copyDirectory(getConfigPath().resolve("slide/frameworks"), folderPath); indikatorService.stopProgressBar(); threadService.runActionLater(() -> { tabService.addTab(folderPath.resolve("slide.adoc")); }); directoryService.changeWorkigDir(folderPath); }); } }); } @FXML public void addToFavoriteDir(ActionEvent actionEvent) { Path selectedTabPath = tabService.getSelectedTabPath(); if (Files.isDirectory(selectedTabPath)) { ObservableList<String> favoriteDirectories = storedConfigBean.getFavoriteDirectories(); boolean has = favoriteDirectories.contains(selectedTabPath.toString()); if (!has) { favoriteDirectories.add(selectedTabPath.toString()); } } } public EditorConfigBean getEditorConfigBean() { return editorConfigBean; } @FXML public void showSettings() { } @WebkitCall(from = "editor-shortcut") public void showWorkerPane() { threadService.runActionLater(() -> { ToggleButton toggleButton = new ToggleButton(); toggleButton.setPrefSize(20, 80); toggleButton.setToggleGroup(rightToggleGroup); toggleButton.getStyleClass().addAll("corner-toggle-button", "corner-bottom-half"); final Label label = new Label("Worker"); label.setRotate(90); toggleButton.setGraphic(new Group(label)); toggleButton.setPadding(Insets.EMPTY); toggleButton.setOnAction(this::toggleWorkerView); toggleButton.fire(); final ObservableList<Node> children = rightTooglesBox.getChildren(); children.add(toggleButton); }); } private void toggleWorkerView(ActionEvent actionEvent) { rightShowerHider.showNode(asciidocWebkitConverter); } public void addRemoveRecentList(Path path) { if (Objects.isNull(path)) return; threadService.runActionLater(() -> { storedConfigBean.getRecentFiles().remove(new Item(path)); storedConfigBean.getRecentFiles().add(0, new Item(path)); }); } public void goUp(ActionEvent actionEvent) { directoryService.goUp(); } @FXML public void copyPath(ActionEvent actionEvent) { Path path = tabService.getSelectedTabPath(); this.cutCopy(path.toString()); } public void closeAllTabs(Event event) { ObservableList<Tab> tabs = FXCollections.observableArrayList(tabPane.getTabs()); tabs.stream() .filter(t -> t instanceof MyTab) .map(t -> (MyTab) t).sorted((mo1, mo2) -> { if (mo1.isNew() && !mo2.isNew()) return -1; else if (mo2.isNew() && !mo1.isNew()) { return 1; } return 0; }).forEach(myTab -> { if (event.isConsumed()) return; ButtonType close = myTab.close(); if (close == ButtonType.CANCEL) event.consume(); }); } public void openTerminalItem(ActionEvent actionEvent) { Path selectedTabPath = tabService.getSelectedTabPath(); if (Objects.nonNull(selectedTabPath)) { if (!Files.isDirectory(selectedTabPath)) { selectedTabPath = selectedTabPath.getParent(); } } newTerminal(actionEvent, selectedTabPath); } public void includeAsSubdocument() { String selection = current.currentEditor().editorSelection(); DialogBuilder fileDialog = DialogBuilder.newFileDialog(); Optional<String> filenameOptional = fileDialog.showAndWait().map(String::trim); if (filenameOptional.isPresent()) { String filename = filenameOptional.get(); Path parent = current.currentTab().getParentOrWorkdir(); Path path = parent.resolve(filename); IOHelper.createDirectories(path.getParent()); Optional<Exception> exception = IOHelper.writeToFile(path, selection, CREATE_NEW, WRITE); if (!exception.isPresent()) { current.currentEditor().removeToLineStart(); current.currentEditor().insert(String.format("\ninclude::%s[]\n", filename)); tabService.addTab(path); } } } public VBox getConfigBox() { return configBox; } @WebkitCall(from = "asciidoctor-image-cache") public Integer readImageCache(String target) { return current.getCache().get(target); } public String applyReplacements(String text) { return converterProvider.get(previewConfigBean).applyReplacements(text); } @FXML public void toggleRecentView(ActionEvent actionEvent) { final ToggleButton source = (ToggleButton) actionEvent.getSource(); splitPane.setDividerPosition(0, source.isSelected() ? 0.17 : 0); if (source.isSelected()) { leftShowerHider.showNode(recentListView); } } @FXML public void toggleWorkdirView(ActionEvent actionEvent) { final ToggleButton source = (ToggleButton) actionEvent.getSource(); splitPane.setDividerPosition(0, source.isSelected() ? 0.17 : 0); if (source.isSelected()) { leftShowerHider.showDefaultNode(); } } @FXML public void toggleOutlineView(ActionEvent actionEvent) { final ToggleButton source = (ToggleButton) actionEvent.getSource(); splitPane.setDividerPosition(0, source.isSelected() ? 0.17 : 0); if (source.isSelected()) { leftShowerHider.showNode(outlineTreeView); } } @FXML public void togglePreviewView(ActionEvent actionEvent) { final ToggleButton source = (ToggleButton) actionEvent.getSource(); splitPane.setDividerPosition(1, source.isSelected() ? 0.59 : 1); if (source.isSelected()) { rightShowerHider.showDefaultNode(); } } @FXML public void toggleConfigurationView(ActionEvent actionEvent) { final ToggleButton source = (ToggleButton) actionEvent.getSource(); splitPane.setDividerPosition(1, source.isSelected() ? 0.59 : 1); if (source.isSelected()) { rightShowerHider.showNode(configBox); } } @FXML public void toggleLogView(ActionEvent actionEvent) { final ToggleButton source = (ToggleButton) actionEvent.getSource(); source.getStyleClass().remove("red-label"); mainVerticalSplitPane.setDividerPosition(0, source.isSelected() ? editorConfigBean.getVerticalSplitter() : 1); if (source.isSelected()) { bottomShowerHider.showNode(logVBox); } } @FXML public void toggleTerminalView(ActionEvent actionEvent) { final ToggleButton source = (ToggleButton) actionEvent.getSource(); mainVerticalSplitPane.setDividerPosition(0, source.isSelected() ? editorConfigBean.getVerticalSplitter() : 1); if (source.isSelected()) { bottomShowerHider.showNode(terminalHBox); if (terminalTabPane.getTabs().isEmpty()) { newTerminal(null); } } } public ShowerHider getRightShowerHider() { return rightShowerHider; } public void setHostServices(HostServices hostServices) { this.hostServices = hostServices; } public HostServices getHostServices() { return hostServices; } public void setConfigToggleGroup(ToggleGroup configToggleGroup) { this.configToggleGroup = configToggleGroup; } public ToggleGroup getConfigToggleGroup() { return configToggleGroup; } public String getClipboardImageFilePattern() { return editorConfigBean.getClipboardImageFilePattern(); } public void initializeTabWatchListener() { getTabPane().getTabs().addListener((ListChangeListener<Tab>) c -> { c.next(); List<? extends Tab> addedSubList = c.getAddedSubList(); threadService.runTaskLater(() -> { tabService.applyForEachMyTab(myTab -> { fileWatchService.registerPathWatcher(myTab.getPath()); }, addedSubList); }); }); } public int getHangFileSizeLimit() { return editorConfigBean.getHangFileSizeLimit(); } public void applyCurrentTheme(Scene... scenes) { EditorConfigBean.Theme theme = editorConfigBean.getEditorTheme().get(0); String themeUri = theme.themeUri(); if (Objects.isNull(themeUri)) { return; } threadService.runActionLater(() -> { try { String aceTheme = theme.getAceTheme(); // editorConfigBean.updateAceTheme(aceTheme); for (Scene scene : scenes) { if (Objects.nonNull(scene)) { ObservableList<String> stylesheets = scene.getStylesheets(); stylesheets.clear(); stylesheets.add(themeUri); } } terminalConfigBean.changeTheme(theme); } catch (Exception e) { logger.error("Error occured while setting new theme {}", theme); } }); } public void applyTheme(EditorConfigBean.Theme theme) { String themeUri = theme.themeUri(); if (Objects.isNull(themeUri)) { return; } threadService.runActionLater(() -> { try { Scene[] scenes = {scene}; for (Scene scene : scenes) { if (Objects.nonNull(scene)) { ObservableList<String> stylesheets = scene.getStylesheets(); stylesheets.clear(); stylesheets.add(themeUri); } } String aceTheme = theme.getAceTheme(); editorConfigBean.updateAceTheme(aceTheme); terminalConfigBean.changeTheme(theme); } catch (Exception e) { logger.error("Error occured while setting new theme {}", theme); } }); } public void setAsciidocTableScene(Scene asciidocTableScene) { this.asciidocTableScene = asciidocTableScene; } public Scene getAsciidocTableScene() { return asciidocTableScene; } public void setMarkdownTableScene(Scene markdownTableScene) { this.markdownTableScene = markdownTableScene; } public Scene getMarkdownTableScene() { return markdownTableScene; } }