package com.kodcu.component; import com.kodcu.config.StoredConfigBean; import com.kodcu.controller.ApplicationController; import com.kodcu.other.ExtensionFilters; import com.kodcu.other.IOHelper; import com.kodcu.other.Item; import com.kodcu.service.DirectoryService; import com.kodcu.service.ThreadService; import com.kodcu.service.shortcut.AsciidocShortcutService; import com.kodcu.service.shortcut.HtmlShortcutService; import com.kodcu.service.shortcut.MarkdownShortcutService; import com.kodcu.service.shortcut.NoneShortcutService; import com.kodcu.service.ui.TabService; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.ObservableList; import javafx.scene.control.ButtonType; import javafx.scene.control.Label; import javafx.scene.control.Tab; import javafx.stage.FileChooser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.FileTime; import java.util.Objects; import java.util.Optional; import static java.nio.file.StandardOpenOption.*; /** * Created by usta on 17.12.2014. */ @Component @Scope("prototype") public class MyTab extends Tab { private final EditorPane editorPane; private final StoredConfigBean storedConfigBean; private final DirectoryService directoryService; private final TabService tabService; private final ApplicationController controller; private final ThreadService threadService; private final Logger logger = LoggerFactory.getLogger(MyTab.class); @Autowired public MyTab(EditorPane editorPane, StoredConfigBean storedConfigBean, DirectoryService directoryService, TabService tabService, ApplicationController controller, ThreadService threadService) { this.editorPane = editorPane; this.storedConfigBean = storedConfigBean; this.directoryService = directoryService; this.tabService = tabService; this.controller = controller; this.threadService = threadService; changedPropertyProperty().addListener((observable, oldValue, newValue) -> { if (newValue) { if (!getStyleClass().contains("mytab-changed")) getStyleClass().add("mytab-changed"); } else { getStyleClass().remove("mytab-changed"); } }); } public Label getLabel() { if (Objects.isNull(this.getGraphic())) this.setGraphic(new Label()); return (Label) this.getGraphic(); } public String getTabText() { Label label = getLabel(); return label.getText(); } public void setTabText(String tabText) { Label label = getLabel(); label.setText(tabText); } public Path getPath() { if (Objects.isNull(editorPane)) { return null; } return editorPane.getPath(); } public void setPath(Path path) { if (Objects.nonNull(editorPane)) { editorPane.setPath(path); } } public boolean isSaved() { // return !this.getTabText().contains(" *"); return !this.editorPane.getChangedProperty(); } public ButtonType close() { if (Objects.nonNull(getPath())) tabService.getClosedPaths().add(Optional.ofNullable(getPath())); this.select(); if (isNew() && !isChanged()) { // if tab is not dirty closeIt(); return ButtonType.YES; } else if (isNew()) { // else if is new ButtonType type = AlertHelper.saveAlert().orElse(ButtonType.CANCEL); if (type == ButtonType.YES) { closeIt(); } return type; } else { // others should be save and close saveDoc(); if (isSaved()) { closeIt(); return ButtonType.YES; } else { ButtonType type = AlertHelper.saveAlert().orElse(ButtonType.CANCEL); if (type == ButtonType.YES) { closeIt(); } return type; } } } private boolean isDirty() { if (isNew()) { if (Objects.nonNull(editorPane)) { try { String value = editorPane.getEditorValue(); if ("".equals(value)) return false; } catch (Exception e) { // no-op } } } return true; } public boolean isChanged() { // String tabText = getTabText(); // return tabText.contains(" *"); return this.editorPane.getChangedProperty(); } public synchronized void reload() { Optional.ofNullable(getPath()) .filter(Files::exists) .ifPresent(path -> { FileTime latestModifiedTime = IOHelper.getLastModifiedTime(path); if (Objects.nonNull(latestModifiedTime) && Objects.nonNull(getLastModifiedTime())) { if (latestModifiedTime.compareTo(getLastModifiedTime()) > 0) { if (isChanged()) { this.select(); ButtonType buttonType = AlertHelper.conflictAlert(getPath()).orElse(ButtonType.CANCEL); if (buttonType != AlertHelper.LOAD_FILE_SYSTEM_CHANGES) { return; } } load(); } } }); } public synchronized void load() { FileTime latestModifiedTime = IOHelper.getLastModifiedTime(getPath()); setLastModifiedTime(latestModifiedTime); String content = IOHelper.readFile(getPath()); editorPane.setEditorValue(content); this.select(); setTabText(getPath().getFileName().toString()); setChangedProperty(false); } private synchronized void save() { FileTime latestModifiedTime = IOHelper.getLastModifiedTime(getPath()); if (Objects.nonNull(latestModifiedTime) && Objects.nonNull(getLastModifiedTime())) { if (latestModifiedTime.compareTo(getLastModifiedTime()) > 0) { this.select(); ButtonType buttonType = AlertHelper.conflictAlert(getPath()).orElse(ButtonType.CANCEL); if (buttonType == ButtonType.CANCEL) { return; } if (buttonType == AlertHelper.LOAD_FILE_SYSTEM_CHANGES) { load(); } } else { if (!isNew() && !isChanged()) { return; } } } if (Objects.isNull(getPath())) { final FileChooser fileChooser = directoryService.newFileChooser(String.format("Save file")); fileChooser.getExtensionFilters().addAll(ExtensionFilters.ASCIIDOC); fileChooser.getExtensionFilters().addAll(ExtensionFilters.MARKDOWN); fileChooser.getExtensionFilters().addAll(ExtensionFilters.ALL); File file = fileChooser.showSaveDialog(null); if (Objects.isNull(file)) return; setPath(file.toPath()); setTabText(file.toPath().getFileName().toString()); } String editorValue = editorPane.getEditorValue(); IOHelper.createDirectories(getPath().getParent()); Optional<Exception> exception = IOHelper.writeToFile(getPath(), editorValue, TRUNCATE_EXISTING, CREATE, SYNC); if (exception.isPresent()) { return; } setLastModifiedTime(IOHelper.getLastModifiedTime(getPath())); setChangedProperty(false); ObservableList<Item> recentFiles = storedConfigBean.getRecentFiles(); recentFiles.remove(new Item(getPath())); recentFiles.add(0, new Item(getPath())); directoryService.setInitialDirectory(Optional.ofNullable(getPath().toFile())); } public void saveDoc() { threadService.runActionLater(this::save); } public boolean isNew() { return "new *".equals(this.getTabText()); } public void select() { this.getTabPane().getSelectionModel().select(this); } private void closeIt() { threadService.runActionLater(() -> { tabService.getClosedPaths().add(Optional.ofNullable(getPath())); this.getTabPane().getTabs().remove(this); // keep it here ObservableList<Tab> tabs = controller.getTabPane().getTabs(); if (tabs.isEmpty()) { tabService.newDoc(); } }, true); } @Override public String toString() { return getTabText(); } public Class getShortcutType() { if (isAsciidoc()) { return AsciidocShortcutService.class; } else if (isMarkdown()) { return MarkdownShortcutService.class; } else if (isHTML()) { return HtmlShortcutService.class; } return NoneShortcutService.class; } public boolean isMarkdown() { return editorPane.isMarkdown(); } public boolean isHTML() { return editorPane.isHTML(); } public boolean isAsciidoc() { return editorPane.isAsciidoc(); } public String htmlToMarkupFunction() { return isAsciidoc() ? "toAsciidoc" : "toMarkdown"; } public EditorPane getEditorPane() { return editorPane; } public FileTime getLastModifiedTime() { if (Objects.isNull(editorPane)) return null; return editorPane.getLastModifiedTime(); } public void setLastModifiedTime(FileTime lastModifiedTime) { if (Objects.nonNull(editorPane)) { this.editorPane.setLastModifiedTime(lastModifiedTime); } } public boolean getChangedProperty() { return this.editorPane.getChangedProperty(); } public BooleanProperty changedPropertyProperty() { return this.editorPane.changedPropertyProperty(); } public void setChangedProperty(boolean changedProperty) { this.editorPane.setChangedProperty(changedProperty); } public Path getParentOrWorkdir() { return Optional.ofNullable(getPath()).map(Path::getParent).orElse(directoryService.workingDirectory()); } }