1

I've created a simple text editor that autosaves txt files every minute and also spell checks the TextArea with the languagetool java api. I'd like to have the autosaving done when the user stops typing and also have the spellchecking done when the user stops typing as well. I think I can do it some way with the OnKeyPressed and OnKeyRealesed methods associated with the textarea and use System.currentTimeMillis method but I have not been able to figure that out. I also thought it’d be best to do these processes in another thread but I was unsure how to implement that.

The auto saving and spell checking is done with the AutoSave and AutoSaveMac Class that use TimerTask to create a task that then runs every min. These Classes are used in the runAutoSave() function that runs on app initialization.

So I guess my question is how can I run the saving and spell checking in another thread when the user stops typing?

Please let me know if I can answer any other questions whatsoever.

EDIT: I've since started testing with the lines of code for the languagetool api removed/commented out. (In FXMLController.java and pom.xml) Way quicker for testing as the languagetool api takes a bit to load on run. I figure if i can get saving working when user stops typing then i'll easily be able to add the languagetool api code.

FXMLController.java

package com.mycompany.maveneditormre;

import java.io.File;
import java.io.FileWriter;
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.util.List;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javax.swing.JFileChooser;
//import org.languagetool.JLanguageTool;
//import org.languagetool.language.BritishEnglish;
//import org.languagetool.rules.RuleMatch;

public class FXMLController implements Initializable {
    String opSystem = new String();
    String documentsPath = new JFileChooser().getFileSystemView().getDefaultDirectory().toString();
    @FXML public TextArea textArea;
    @FXML public TextArea spellcheckTextArea;
    @FXML public TextField textFieldFileFolderName;
    private final TextArea textArea2 = new TextArea();
    public static volatile int keyPressTime = 00;
    public static volatile int keyReleaseTime = 00;
//    JLanguageTool langTool = new JLanguageTool(new BritishEnglish());
    
    
    public class FirstThread implements Runnable {
        
        public FirstThread(){
        
        }
    
        public void run(){
        
        System.out.println("MyThread running");
       
       
        }
    
    }
    
    @FXML
    private void handleKeyPressed() throws IOException {
        keyPressTime = (int) System.currentTimeMillis();
        
    }
    
    @FXML
    private void handleKeyReleased() throws IOException{
        keyReleaseTime = (int) System.currentTimeMillis();
          
    }
    
    private void Save(String content, File file){
        try {
            FileWriter fileWriter;
            fileWriter = new FileWriter(file);
            fileWriter.write(content);
            fileWriter.close();
        } catch (IOException ex) {
            Logger.getLogger(FXMLController.class
                .getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    private void runAutoSave(){
        if (this.opSystem.contains("mac")){
                Timer timer = new Timer();
                TimerTask task = new AutoSaveMac();
                timer.schedule(task, 2000, 60000);
            } else {
                Timer timer = new Timer();
                TimerTask task = new AutoSave();
                timer.schedule(task, 2000, 60000);
            }
    
    
    }
    
    class AutoSave extends TimerTask {
      
        @Override
        public void run() {
             
            if(textArea2.getText().equals(textArea.getText())){
                // do nothing
            } else {
                // autosave new folder and file if needed
//                String[] firstLineOfTextArea = textArea.getText().split("\\n");
                    String[] firstLineOfTextArea = textFieldFileFolderName.getText().split("\\n");
                    String[] folderAndFileName = firstLineOfTextArea[0].split("-", 2);
                    Path path = Paths.get(documentsPath + "\\ssef\\" + folderAndFileName[0]);
                if (Files.exists(path)) {
                    // Do nothing
                } else {
                    new File(documentsPath + "\\ssef\\" + folderAndFileName[0]).mkdir();
                }
                File file = new File(String.format(documentsPath + "\\ssef\\" + folderAndFileName[0]+ "\\%s.txt", folderAndFileName[1] ));
                Save(textArea.getText().replaceAll("\n", System.getProperty("line.separator")), file);
                textArea2.setText(textArea.getText());
                
//                spell check
//                List<RuleMatch> matches = null;
//                        try {
//                            matches = langTool.check(textArea.getText());
//                            System.out.println(matches);
//                        } catch (IOException ex) {
//                            Logger.getLogger(FXMLController.class.getName()).log(Level.SEVERE, null, ex);
//                        }
//                        
//                        if (matches.isEmpty() == true) { 
//                            spellcheckTextArea.clear();
//                        }
//                            
//                            for (RuleMatch match : matches) {
//                            spellcheckTextArea.setText(spellcheckTextArea.getText() + "\n" + "Potential error at characters " + match.getFromPos() + "-" + match.getToPos() + ": " + match.getMessage() + "\n" + "Suggested correction(s): " + match.getSuggestedReplacements());
//
//                            System.out.println("Potential error at characters " + match.getFromPos() + "-" + match.getToPos() + ": " + match.getMessage());
//                            System.out.println("Suggested correction(s): " + match.getSuggestedReplacements());
//                            }
                        
                    
            
            }
        } 
    }
    
    class AutoSaveMac extends TimerTask {
      
        @Override
        public void run() {
             
            if(textArea2.getText().equals(textArea.getText())){
                // do nothing
            } else {
                // autosave new folder and file if needed
                String[] firstLineOfTextArea = textFieldFileFolderName.getText().split(System.getProperty("line.separator"));
                System.out.println(firstLineOfTextArea[0] + " first line of textarea");
                String[] folderAndFileName = firstLineOfTextArea[0].split("-", 2);
                Path path = Paths.get(documentsPath + "/Documents" + "/ssef/" + folderAndFileName[0]);
                
                if (Files.exists(path)) {
                    // Do nothing
                } else {
                    new File(documentsPath +  "/Documents" + "/ssef/" + folderAndFileName[0]).mkdir();
                }
                File file = new File(documentsPath + "/Documents" + "/ssef/" + folderAndFileName[0] + "/" + folderAndFileName[1]+ ".txt" );
                Save(textArea.getText().replaceAll("/n", System.getProperty("line.separator")), file);
                textArea2.setText(textArea.getText());
                
//                List<RuleMatch> matches = null;
//                        try {
//                            matches = langTool.check(textArea.getText());
//                            System.out.println(matches);
//                        } catch (IOException ex) {
//                            Logger.getLogger(FXMLController.class.getName()).log(Level.SEVERE, null, ex);
//                        }
//                        
//                        if (matches.isEmpty() == true) { 
//                            spellcheckTextArea.clear();
//                        }
//                            
//                            for (RuleMatch match : matches) {
//                            spellcheckTextArea.setText(spellcheckTextArea.getText() + "\n" + "Potential error at characters " + match.getFromPos() + "-" + match.getToPos() + ": " + match.getMessage() + "\n" + "Suggested correction(s): " + match.getSuggestedReplacements());
//
//                            System.out.println("Potential error at characters " + match.getFromPos() + "-" + match.getToPos() + ": " + match.getMessage());
//                            System.out.println("Suggested correction(s): " + match.getSuggestedReplacements());
//                            }
            
            }
        } 
    } 
    
    private void createOpeningFolder(){
        
        if (opSystem.contains("mac")) {
            Path path = Paths.get(documentsPath + "/Documents" + "/ssef");
            if (Files.exists(path)) {
                    // Do nothing
            } else {
            new File(documentsPath + "/Documents" + "/ssef").mkdir();
            }
        
        } else {
            Path path = Paths.get(documentsPath + "\\ssef");
            if (Files.exists(path)) {
                    // Do nothing
            } else {
            new File(documentsPath + "\\ssef").mkdir();
            }
        }
        
    }
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
        String osName = System.getProperty("os.name").toLowerCase();
        this.opSystem = osName;
        System.out.println("Operating System: "   + this.opSystem);
        textFieldFileFolderName.setFocusTraversable(true);
        textArea.setWrapText(true);
        Runnable firstThread = new FirstThread();
        new Thread(firstThread).start();
        createOpeningFolder();
        runAutoSave();
    }    
}



scene.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<BorderPane fx:id="borderPaneRoot" prefHeight="600.0" prefWidth="1000.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="com.mycompany.maveneditormre.FXMLController">

    <center>
    <GridPane>
      <children>
        <TextField fx:id="textFieldFileFolderName" prefWidth="200.0" promptText="foldername-filename" GridPane.columnIndex="0" GridPane.rowIndex="0"    />
        <TextArea fx:id="textArea" prefWidth="200.0" promptText="note taking" wrapText="true" GridPane.columnIndex="0" GridPane.rowIndex="1" onKeyPressed="#handleKeyPressed" onKeyReleased="#handleKeyReleased" />
      </children>
      <columnConstraints>
        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
      </columnConstraints>
      <rowConstraints>
        <RowConstraints maxHeight="202.0" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
        <RowConstraints maxHeight="376.0" minHeight="10.0" prefHeight="376.0" vgrow="SOMETIMES" />
      </rowConstraints>
    </GridPane>
  </center>
  
  <bottom>
    <TextArea fx:id="spellcheckTextArea" editable="false" promptText="spellchecking" wrapText="true" />
  </bottom>
  
</BorderPane>

Be sure to add the following to pom.xml

<dependencies>
        <dependency>
            <groupId>org.languagetool</groupId>
            <artifactId>language-en</artifactId>
            <version>4.8</version>
        </dependency>
    </dependencies>
  • This seems like it may work. https://stackoverflow.com/questions/31955193/how-to-start-a-function-after-stop-typing-in-a-jtextfield-in-java - Could I 'Use a Swing Timer and a DocumentListener, each time the Document is updated, reset the Timer' ???? attempting... – fletchlives Oct 28 '20 at 22:52
  • 1
    Swing timer and document listener are not suitable for JavaFX. Maybe this is more appropriate: https://stackoverflow.com/questions/9966136/javafx-periodic-background-task – Abra Oct 29 '20 at 05:38
  • [mcve] please (nothing unrelated like reading a file, f.i, and everything to demonstrate what you are after, which problem exactly stands in the way and reproducible!) and stick to java naming conventions – kleopatra Oct 29 '20 at 17:39
  • Thank you @Abra! – fletchlives Oct 29 '20 at 21:19
  • On it @kleopatra. Thank you! – fletchlives Oct 29 '20 at 21:20
  • @kleopatra, I've remove as much unnessesary code as I thought was appropriate. I also added a code block that needs to be added to pom.xml for the languagetool api to function. Please let me know if I can any other questions or make any other improvements whatsoever. Thanks again. – fletchlives Oct 29 '20 at 23:42
  • I've since started testing with the lines of code for the languagetool api removed/commented out. (In FXMLController.java and pom.xml) Way quicker for testing as the languagetool api takes a bit to load on run. I figure if i can get saving working when user stops typing then i'll easily be able to add the languagetool api code. code above is updated. – fletchlives Nov 04 '20 at 04:18
  • better, but keep cleaning up the code (commented code is just clutter, and violations of java naming conventions makes it hard to read) - that said: the single most important rule in threading is to __not__ modify nodes nor their properties off the fx application thread! Learn all about how javafx concurrency support helps you reach your goal (and do __not__ use core Timer/-Task), apply that to your context and when stuck, come back with a [mcve] using it. – kleopatra Nov 04 '20 at 09:48
  • Does this answer your question? [JavaFX Spell checker using RichTextFX how to create right click suggestions](https://stackoverflow.com/questions/72204764/javafx-spell-checker-using-richtextfx-how-to-create-right-click-suggestions) – trilogy May 19 '22 at 01:26

0 Answers0