Read and edit a text file (Editor)

Hi everyone, I was wondering if anyone has a simple program that can read each line of a txt-file and edit a specific line if you want to.
Thank you to everyone who can help!

1 Like

Hi

here is a basic Sketch to load and save text

you cannot edit text really, except for Backspace

  • it’s originally a few tabs, joined into one Sketch now

// ********************************************************************************
//         joined pde-file of folder 
// ********************************************************************************


// ********************************************************************************
// tab: Editor1.pde main file (file name is folder name)



// Editor
// Demo for Editor and Save and load handling
// SEE 3 buttons lower right corner !!!!!!!!!!!!!!!!!!!!!!!!

// SKETCH EXPECTS A SUB FOLDER texts in the Sketch folder, see: final String pathFolder    = "texts";
// 
//
// from https : // forum.processing.org/two/discussion/comment/112902/#Comment_112902

// editor path and file extension
final String pathFolder    = "texts";
final String fileExtension = ".txt";

// editor content 
String str = "Test ";

// states of the program:
// unique constants: 
final int normal = 0;
final int save   = 1;
final int load   = 2;
///current state (must be one of them) 
int state=normal;

// blinking cursor:  
boolean blinkIsOn=true; 

// Paths (returned by 'callback' functions)
String savePath=""; 
String loadPath=""; 

// ------------------------------------------------
// Core functions of processing 

void setup() {
  size(900, 900);
}//func 

void draw() {

  switch (state) {

  case normal:
    drawForStateNormal() ;
    break; 

  case save:
    // wait for Save Dialog 
    waitForSaveDialog();
    break; 

  case load:
    // wait for Load Dialog 
    waitForLoadDialog();
    break;

  default:
    //Error 
    println("Error: 4711");
    exit();
    break; 
    //
  }//switch
  //
}//func
//

// ********************************************************************************
// tab: InputsKeys.pde


// keep it simple 

void keyPressed() {

  if (state!=normal)
    return;

  // for the editor: 
  if ( keyCode == DELETE || keyCode == BACKSPACE ) {
    if ( str.length() > 0 ) {
      str = str.substring(0, str.length()-1);
    }
  } else {
    if ( key != CODED ) {
      str += key;
    }
  }
}
//

// ********************************************************************************
// tab: InputsMouse1.pde


//

void mousePressed() {

  if (state!=normal)
    return;

  // for the buttons 
  if ( overSave() ) {
    initSave();
  }
  //---
  else if ( overLoad() ) {
    initLoad();
  }
  //---
  else if ( overNew() ) {
    str="";
  }
  //
}//func

// ----------------------------------------------------------
// functions to register if mouse is over buttons 

boolean overSave() {
  return( mouseX > width-40 && 
    mouseY > height-20 );
}

boolean overLoad() {
  return( mouseX > width-40 && 
    mouseY > height-50  && 
    mouseY < height-50+25 );
}

boolean overNew() {
  return( mouseX > width-40 && 
    mouseY > height-80  && 
    mouseY < height-80+25 );
}
//

// ********************************************************************************
// tab: SaveLoad.pde


// Save and load 

// 4 blocks with 2 functions each, for save and load 

void initSave() {
  // init save process 
  // reset
  savePath="";
  // make date time stamp (the expression nf(n,2) means leading zero: 2 becomes 02)
  String dateTimeStamp = year() 
    + nf(month(), 2) 
    + nf(day(), 2) 
    + "-" 
    + nf(hour(), 2)
    + nf(minute(), 2)
    + nf(second(), 2);
  // prepare fileDescription which occurs in the dialogue
  File fileDescription = new File( sketchPath()
    + "//"
    + pathFolder 
    + "//" 
    + dateTimeStamp
    + fileExtension);
  // open the dialog  
  selectOutput("Select a file to write to", "fileSelectedSave", fileDescription);
  // set state to wait
  state=save;
}

void initLoad() {
  // init load process 
  // reset
  loadPath="";
  // prepare fileDescription which occurs in the dialogue
  File fileDescription = new File( sketchPath()+"//"+pathFolder+"//"+"*" + fileExtension );
  // open the dialog
  selectInput("Select a file to load", "fileSelectedLoad", fileDescription);
  // set state to wait
  state=load;
}

// --------------------------------------------------------------------

void fileSelectedSave(File selection) {
  // the 'callback' function
  if (selection == null) {
    // println("Window was closed or the user hit cancel.");
    // go back 
    state=normal;
  } else {
    // println("User selected " + selection.getAbsolutePath());
    savePath=selection.getAbsolutePath();
  }
}

void fileSelectedLoad(File selection) {
  // the 'callback' function
  if (selection == null) {
    // println("Window was closed or the user hit cancel.");
    // go back 
    state=normal;
  } else {
    // println("User selected " + selection.getAbsolutePath());
    loadPath=selection.getAbsolutePath();
  }
}

// ----------------------------------------------------
// waiting 

void waitForSaveDialog() { 
  if (!savePath.equals("")) {
    // waiting is over
    saveIt();
    // go back 
    state=normal;
  }
}

void waitForLoadDialog() { 
  if (!loadPath.equals("")) {
    // waiting is over
    loadIt();
    // go back 
    state=normal;
  }
}

// ----------------------------------------------------

void saveIt() {
  // save
  // split at line break and make array (to save it)
  String[] strs = split ( str, "\n" );
  // check if file extension (fileExtension, e.g. .txt) is there 
  int len = savePath.length(); 
  if (len<4 || !savePath.substring( len-4 ).equals(fileExtension)) {
    // file Extension is not present, we have to add it
    savePath += fileExtension; // add the file Extension
  } 
  // save 
  println("Saved: " + savePath);
  saveStrings( savePath, strs );
}

void loadIt() {
  // load
  String[] strs = loadStrings( loadPath );
  str = join(strs, "\n");
}
//

// ********************************************************************************
// tab: States.pde


void drawForStateNormal() {

  background(0);

  textSize(14);

  // title 
  fill(255, 2, 2);
  text("My Little Editor", 
    width-133, 20, 
    130, 422);

  // show the text the user entered 
  fill(255);
  text(str+blink(), 
    20, 20, width-177, height-20);

  // ----------------------
  // buttons
  textSize(11);
  fill(128);
  if ( overSave() ) {
    fill(196);
  }
  rect(width-40, height-20, 40, 20);
  fill(255); 
  text("Save", 
    width-40+7, height-9+5);

  // ---
  fill(128);
  if ( overLoad() ) {
    fill(196);
  }
  rect(width-40, height-50, 40, 20);
  fill(255); 
  text("Load", 
    width-40+7, height-50+9+5);

  // ---
  fill(128);
  if ( overNew() ) {
    fill(196);
  }
  rect(width-40, height-80, 40, 20);
  fill(255); 
  text("New", 
    width-40+7, height-80+9+5);
}

// ********************************************************************************
// tab: Tools.pde


// Misc

String blink() {
  // toggle blinkIsOn
  if (frameCount%17 == 0) 
    blinkIsOn=!blinkIsOn;

  // depending from blinkIsOn 
  if (blinkIsOn) 
    return "|";
  else return "";
}
//

// End of joined file. ********************************************************************************


To read a table I can just use loadStrings without any problems, but the problem I am having now is instead of editing the specific file I need to make a new txt-file that can replace the old one with the text i want.
Thank you for the previous answers

Another option would be to use JTextArea in a Swing app. Downside is you have to do your own event handling to add ‘Open’ and ‘Save’ buttons. Also draw() is non-functional with this approach.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

javax.swing.JFrame frame;
java.awt.Canvas canvas;

JTextArea txtArea;

final int _wndW = 600;
final int _wndH = 600;

void textArea() {
  txtArea = new JTextArea();
  JScrollPane scrlPane = new JScrollPane(txtArea);
  scrlPane.setBounds(0, 40, _wndW, _wndH);
  frame.add(scrlPane);
  txtArea.setEditable(true);
 // txtArea.setFont(new Font("Menlo", Font.BOLD, 16));
  txtArea.setLineWrap(false);
  txtArea.setWrapStyleWord(true);
  txtArea.repaint();
}

void buildWnd(){
  textArea();
}

void setup() {
  frame = (javax.swing.JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame();
  canvas = (processing.awt.PSurfaceAWT.SmoothCanvas) ((processing.awt.PSurfaceAWT)surface).getNative();  
  frame.setBounds(500, 300, _wndW, _wndH);
  frame.remove(canvas);
  surface.setTitle("JTextArea Demo");
  
  javax.swing.SwingUtilities.invokeLater(new Runnable() {
    public void run() {
      buildWnd(); // Builds components on EventDispatchThread
    }
  }
  );
}

Honestly, I feel that using Java 's File API is the only thing worthy of use here.

What I should tell you, though, is that this would require a lot of learning and some practice.

I myself learnt to use the File API pretty recently, so I consider myself worthy enough to write this ‘little’ guide. :rofl:

What I write here in this answer assumes that you first learn about the previous topic I mentioned, so you could be following this in a sort of step-by-step manner later on.

More than enough talk. Here’s the real deal:

The first thing I recommend you to do use another IDE (such as VSCode), and write a few Java programs for File I/O. That should help you learn much faster.

java.io classes such as BufferedReader will help you here.
Here is a JavaDoc! Read a few methods’ names to know what you can do with this class, :D!

Here’s a StackOverflow answer to help you start: How to use BufferedReader in Java - Stack Overflow


The idea, is to make a File object (File file = new File("file_name.txt")). This is basically a way to hold a path to a file, not actually make a file!

You would usually check if the file already exists with the .exists() method, else use .createNewFile()!

You should then make a BufferedReader to read the file. However, a BufferedReader cannot do that alone - you need to feed it a FileReader() first (welcome to the OOP mess, :P)

…that’s what BufferedReader reader = new BufferedReader(new FileReader(file)); is.

“Time for some theory!~”

In C - the programming language the world runs on, we have standard library functions (stuff that comes built into every programming language) for File I/O. The way these work, is by loading a file into memory (RAM), and then proceeding to read each character in it. (Note that modern operating systems of course, will still load the file in small chunks for performance.)

You are then supposed to make changes in the file the way you want to. Once you’re done, you “close the file stream”: this tells the OS to change the contents of the data stored on the disk, to whatever you did to its copy in RAM.

I’m sorry if this sounded like tech jargon and wasn’t understood by you… Anyway, this should tell you the very important things it has to:

  • Yes, Java has a way to read files byte-by-byte (FileInputStream for bytes!).
  • Yes, BufferedReader is supposed to make your work easier by giving you lines to read, rather than giving you just chars, or bytes.
  • You have to remember to “close the file stream”!


Now - making objects from File I/O classes in Java can throw exceptions.
You could make a try { } catch(Exception e) { } block to deal with that.

However, there exists a utility for just this stuff: the “try-with-resources” block! The essence of these, is that they .close() the readers etc. automatically. (Though you would be doing that yourself sometimes. Try doing it manually in those cases!)
…you should go read about it :D!

Having gathered all that information, hopefully, this code should make sense!:

try (BufferedReader reader = new BufferedReader(new FileReader(file))) {

  // The condition in the loop here simply states that `line` should not be `null`.
  // However, before we do that, we simply assign the next line in our file to it.
  // Operators can be used inside round brackets / parenthesis, 
  // ..just like the ternary operator can! 
  // (The `variable = condition? trueVal : falseVal` thing).

  for (String line; (line = reader.readLine()) != null; 
    // Do something like counting the number of lines here!
  ) { // Loop begins:
      println(line);
      //...
    }
}

Next up - classes for writing to the file:

There really are two primary ways: new BufferedWriter(FileWriter(file)), or, as the Processing reference also shows you: new PrintWriter(file).

The difference, is that PrintWriter gives you built-in functionality for formatting (such as automatically inserting the newline character using its println() method).

BufferedWriter, lets you handle exceptions yourself. PrintWriter ‘eats’ them!

…which one to use?!

BufferedWriter.
Even though it only has only one method that can write strings (which you’ll probably have to read the docs for every time), it is faster.

There’s nothing wrong with PrintWriter either, I guess. But speed is nice for larger files!


The important part!:
Note that though you can do both reading and writing at the same time, we won’t need to do that, yay!

How? Using a StringBuilder! (And not a StringBuffer!)

StringBuilder text = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
  for (String line; (line = reader.readLine()) != null;)
    builder.append(line);
}

Using deleteCharAt(), it is possible to edit parts of a line, etcetera, easily.

But we work with Processing!

Using this function, you should be able to write just data/filename for your file locations, and it might work. I have never tested this!

Processing also gives you createReader() and createWriter().

You may want to use Andreas Schlegel’s “controlP5” library to make things easier for a UI.

Also, since Java strings are ‘immutable’ (AKA they remain in memory even when no variable stores them), you would want to use a StringBuilder instead of just a String to hold data. If the file data is too long, it’ll cause memory issues.

controlP5 gives probably gives you the text in another thread (there is a callback function for that!), so you could be using a StringBuffer instead. But apparently, that callback gets a String, which is already sorta’ doing things to memory, so… you could just use that string…




…thanks for reading!

1 Like

Here is a new version based on my previous one.

You can still load and save but you now have an Editor from the g4p library (also a GUI library).

Again, Sketch is in one part, but you can split it up into tabs.

[NEW VERSION BELOW]

// ********************************************************************************
//         joined pde-file of folder 
// ********************************************************************************


// ********************************************************************************
// tab: Editor2.pde main file (file name is folder name)



// Editor ::: using G4P : GTextArea as Editor Element 

// Demo for Editor and Save and load handling
// SEE 3 buttons lower right corner !!!!!!!!!!!!!!!!!!!!!!!!

// SKETCH EXPECTS A SUB FOLDER texts in the Sketch folder, see: final String pathFolder    = "texts";
// 
//
// from https : // forum.processing.org/two/discussion/comment/112902/#Comment_112902

import g4p_controls.*;

GTextArea txaSample;

// editor path and file extension
final String pathFolder    = "texts";
final String fileExtension = ".txt";

// editor content 
// String str = "Test ";

// states of the program:
// unique constants: 
final int normal = 0;
final int save   = 1;
final int load   = 2;
///current state (must be one of them) 
int state=normal;

// blinking cursor:  
boolean blinkIsOn=true; 

// Paths (returned by 'callback' functions)
String savePath=""; 
String loadPath=""; 

// ------------------------------------------------
// Core functions of processing 

void setup() {
  size(900, 900);

  // Create a text area with both horizontal and 
  // vertical scrollbars that automatically hide 
  // when not needed.
  txaSample = new GTextArea(this, 
    20, 20, 
    width-200, height-60, 
    G4P.SCROLLBARS_BOTH | G4P.SCROLLBARS_AUTOHIDE);
  txaSample.setText("Hello", 310);

  // Set some default text
  // txaSample.setPromptText("Please enter some text");
}//func 

void draw() {

  switch (state) {

  case normal:
    drawForStateNormal() ;
    break; 

  case save:
    // wait for Save Dialog 
    waitForSaveDialog();
    break; 

  case load:
    // wait for Load Dialog 
    waitForLoadDialog();
    break;

  default:
    //Error 
    println("Error: 4711");
    exit();
    break; 
    //
  }//switch
  //
}//func
//

// ********************************************************************************
// tab: InputsKeys.pde


// keep it simple 

void keyPressed() {

  if (state!=normal)
    return;

  // for the editor: 
  //if ( keyCode == DELETE || keyCode == BACKSPACE ) {
  //  if ( str.length() > 0 ) {
  //    str = str.substring(0, str.length()-1);
  //  }
  //} else {
  //  if ( key != CODED ) {
  //    str += key;
  //  }
  //}
}
//

// ********************************************************************************
// tab: InputsMouse1.pde


//

void mousePressed() {

  if (state!=normal)
    return;

  // for the buttons 
  if ( overSave() ) {
    initSave();
  }
  //---
  else if ( overLoad() ) {
    initLoad();
  }
  //---
  else if ( overNew() ) {
    // str="";
    txaSample.setText("Hello", 310);
  }
  //
}//func

// ----------------------------------------------------------
// functions to register if mouse is over buttons 

boolean overSave() {
  return( mouseX > width-40 && 
    mouseY > height-20 );
}

boolean overLoad() {
  return( mouseX > width-40 && 
    mouseY > height-50  && 
    mouseY < height-50+25 );
}

boolean overNew() {
  return( mouseX > width-40 && 
    mouseY > height-80  && 
    mouseY < height-80+25 );
}
//

// ********************************************************************************
// tab: SaveLoad.pde



// Save and load 

// 4 blocks with 2 functions each, for save and load 

void initSave() {
  // init save process 
  // reset
  savePath="";
  // make date time stamp (the expression nf(n,2) means leading zero: 2 becomes 02)
  String dateTimeStamp = year() 
    + nf(month(), 2) 
    + nf(day(), 2) 
    + "-" 
    + nf(hour(), 2)
    + nf(minute(), 2)
    + nf(second(), 2);
  // prepare fileDescription which occurs in the dialogue
  File fileDescription = new File( sketchPath()
    + "//"
    + pathFolder 
    + "//" 
    + dateTimeStamp
    + fileExtension);
  // open the dialog  
  selectOutput("Select a file to write to", "fileSelectedSave", fileDescription);
  // set state to wait
  state=save;
}

void initLoad() {
  // init load process 
  // reset
  loadPath="";
  // prepare fileDescription which occurs in the dialogue
  File fileDescription = new File( sketchPath()+"//"+pathFolder+"//"+"*" + fileExtension );
  // open the dialog
  selectInput("Select a file to load", "fileSelectedLoad", fileDescription);
  // set state to wait
  state=load;
}

// --------------------------------------------------------------------

void fileSelectedSave(File selection) {
  // the 'callback' function
  if (selection == null) {
    // println("Window was closed or the user hit cancel.");
    // go back 
    state=normal;
  } else {
    // println("User selected " + selection.getAbsolutePath());
    savePath=selection.getAbsolutePath();
  }
}

void fileSelectedLoad(File selection) {
  // the 'callback' function
  if (selection == null) {
    // println("Window was closed or the user hit cancel.");
    // go back 
    state=normal;
  } else {
    // println("User selected " + selection.getAbsolutePath());
    loadPath=selection.getAbsolutePath();
  }
}

// ----------------------------------------------------
// waiting 

void waitForSaveDialog() { 
  if (!savePath.equals("")) {
    // waiting is over
    saveIt();
    // go back 
    state=normal;
  }
}

void waitForLoadDialog() { 
  if (!loadPath.equals("")) {
    // waiting is over
    loadIt();
    // go back 
    state=normal;
  }
}

// ----------------------------------------------------

void saveIt() {
  // save
  // split at line break and make array (to save it)

  // get the data from the text Area 
  String[] strs = split(txaSample.getText(), "\n" );

  // check if file extension (fileExtension, e.g. .txt) is there 
  int len = savePath.length(); 
  if (len<4 || !savePath.substring( len-4 ).equals(fileExtension)) {
    // file Extension is not present, we have to add it
    savePath += fileExtension; // add the file Extension
  } 
  // save 
  println("Saved: " + savePath);

  saveStrings( savePath, strs );
}

void loadIt() {
  // load
  String[] strs = loadStrings( loadPath );
  String str1 = join(strs, "\n");
  txaSample.setText(str1, 310);
}
//

// ********************************************************************************
// tab: States.pde



void drawForStateNormal() {

  background(0);

  textSize(14);

  // title 
  fill(255, 2, 2);
  text("My Little Editor", 
    width-133, 20, 
    130, 422);

  // show the text the user entered 
  //fill(255);
  //text(str+blink(), 
  //  20, 20, width-177, height-20);

  // ----------------------
  // buttons
  textSize(11);
  fill(128);
  if ( overSave() ) {
    fill(196);
  }
  rect(width-40, height-20, 40, 20);
  fill(255); 
  text("Save", 
    width-40+7, height-9+5);

  // ---
  fill(128);
  if ( overLoad() ) {
    fill(196);
  }
  rect(width-40, height-50, 40, 20);
  fill(255); 
  text("Load", 
    width-40+7, height-50+9+5);

  // ---
  fill(128);
  if ( overNew() ) {
    fill(196);
  }
  rect(width-40, height-80, 40, 20);
  fill(255); 
  text("New", 
    width-40+7, height-80+9+5);
}

// ********************************************************************************
// tab: Tools.pde


// Misc

String blink() {
  // toggle blinkIsOn
  if (frameCount%17 == 0) 
    blinkIsOn=!blinkIsOn;

  // depending from blinkIsOn 
  if (blinkIsOn) 
    return "|";
  else return "";
}
//

// End of joined file. ********************************************************************************

NEW VERSION

new version with button class, better button handling

// ********************************************************************************
//         joined pde-file of folder Editor2
// ********************************************************************************


// ********************************************************************************
// tab: Editor2.pde main file (file name is folder name)



// Editor ::: using G4P : GTextArea as Editor Element 

// Demo for Editor and Save and load handling
// SEE 3 buttons !!!!!!!!!!!!!!!!!!!!!!!!

// SKETCH EXPECTS A SUB FOLDER texts in the Sketch folder, see: final String pathFolder    = "texts";
// 
//
// from https : // forum.processing.org/two/discussion/comment/112902/#Comment_112902

import g4p_controls.*;

GTextArea txaSample;

// editor path and file extension
final String pathFolder    = "texts";
final String fileExtension = ".txt";

// states of the program:
// unique constants: 
final int normal = 0;
final int save   = 1;
final int load   = 2;
///current state (must be one of them) 
int state=normal;

// blinking cursor:  
boolean blinkIsOn=true; 

// Paths (returned by 'callback' functions after using save and load)
String savePath=""; 
String loadPath=""; 

// list of buttons
ArrayList<Button> listButton = new ArrayList(); 

// ------------------------------------------------
// Core functions of processing 

void setup() {
  size(900, 900);

  // Create a text area with both horizontal and 
  // vertical scrollbars that automatically hide 
  // when not needed.
  txaSample = new GTextArea(this, 
    20, 20, 
    width-200, height-60, 
    G4P.SCROLLBARS_BOTH | G4P.SCROLLBARS_AUTOHIDE);
  txaSample.setText("Hello", 310);

  // Set some default text
  // txaSample.setPromptText("Please enter some text");

  // init buttons 
  int i=0;
  listButton.add(new Button ("Save", width-70, 60+i*40, 50, 25));
  i++;
  listButton.add(new Button ("Load", width-70, 60+i*40, 50, 25));
  i++;
  listButton.add(new Button ("New", width-70, 60+i*40, 50, 25));
  i++;
}//func 

void draw() {

  switch (state) {

  case normal:
    drawForStateNormal() ;
    break; 

  case save:
    // wait for Save Dialog 
    waitForSaveDialog();
    break; 

  case load:
    // wait for Load Dialog 
    waitForLoadDialog();
    break;

  default:
    //Error 
    println("Error: 4777");
    exit();
    break; 
    //
  }//switch
  //
}//func
//

// ********************************************************************************
// tab: classButton.pde



class Button {

  String textButton; 
  PVector position = new PVector();
  float sizeW, sizeH; 

  // constructor
  Button (  String s1_, 
    float x_, float y_, 
    float sizeW_, float sizeH_ ) {

    position.set(x_, y_);

    sizeW = sizeW_;
    sizeH = sizeH_;

    textButton=s1_;
  }// constructor

  // ------------------------------------------
  // methods

  void display() {
    // show button 

    // rect
    stroke(200);
    //noFill();
    fill(255); 
    if (checkMouse())
      fill(255, 0, 0); 
    if (checkMouse()&&mousePressed)
      fill(255, 0, 120); 
    rect(position.x, position.y, 
      sizeW, sizeH);

    // text 
    fill(0);
    text(textButton, 
      position.x+5, position.y+17);
  }// method

  boolean checkMouse() {
    // returns true when inside
    return
      mouseX > position.x &&
      mouseX < position.x + sizeW &&
      mouseY > position.y &&
      mouseY < position.y + sizeH;
  } // method
  //
}//class
//

// ********************************************************************************
// tab: InputsKeys.pde


// keep it simple 

void keyPressed() {
  if (state!=normal)
    return;
}
//

// ********************************************************************************
// tab: InputsMouse1.pde


//

void mousePressed() {

  if (state!=normal)
    return;

  String command=""; 

  // for the buttons 
  for (Button currentButton : listButton) {
    if (currentButton.checkMouse()) {
      command=currentButton.textButton; 
      break;
    }
  }//for

  if (command.equals("")) 
    return;

  switch(command) {
  case "Load":
    initLoad();
    break;

  case "Save":
    initSave();
    break;

  case "New":
    txaSample.setText("Hello", 310);
    break;

  default:
    // Error 
    println("unknown command "+command);
    exit();    
    break;
  }//switch 
  //
} //func
//

// ********************************************************************************
// tab: SaveLoad.pde



// Save and load 

// 4 blocks with 2 functions each, for save and load 

// --------------------------------------------------------------------
// the two init functions

void initSave() {
  // init save process 
  // reset
  savePath="";
  // make date time stamp (the expression nf(n,2) means leading zero: 2 becomes 02)
  String dateTimeStamp = year() 
    + nf(month(), 2) 
    + nf(day(), 2) 
    + "-" 
    + nf(hour(), 2)
    + nf(minute(), 2)
    + nf(second(), 2);
  // prepare fileDescription which occurs in the dialogue
  File fileDescription = new File( sketchPath()
    + "//"
    + pathFolder 
    + "//" 
    + dateTimeStamp
    + fileExtension);
  // open the dialog  
  selectOutput("Select a file to write to", "fileSelectedSave", fileDescription);
  // set state to wait
  state=save;
}

void initLoad() {
  // init load process 
  // reset
  loadPath="";
  // prepare fileDescription which occurs in the dialogue
  File fileDescription = new File( sketchPath()+"//"+pathFolder+"//"+"*" + fileExtension );
  // open the dialog
  selectInput("Select a file to load", "fileSelectedLoad", fileDescription);
  // set state to wait
  state=load;
}

// --------------------------------------------------------------------
// the two 'callback' functions

void fileSelectedSave(File selection) {
  // the 'callback' function
  if (selection == null) {
    // println("Window was closed or the user hit cancel.");
    // go back 
    state=normal;
  } else {
    // println("User selected " + selection.getAbsolutePath());
    savePath=selection.getAbsolutePath();
  }
}

void fileSelectedLoad(File selection) {
  // the 'callback' function
  if (selection == null) {
    // println("Window was closed or the user hit cancel.");
    // go back 
    state=normal;
  } else {
    // println("User selected " + selection.getAbsolutePath());
    loadPath=selection.getAbsolutePath();
  }
}

// ----------------------------------------------------
// waiting 

void waitForSaveDialog() { 
  if (!savePath.equals("")) {
    // waiting is over
    saveIt();
    // go back 
    state=normal;
  }
}

void waitForLoadDialog() { 
  if (!loadPath.equals("")) {
    // waiting is over
    loadIt();
    // go back 
    state=normal;
  }
}

// ----------------------------------------------------
// executing save and load

void saveIt() {
  // save
  // split at line break and make array (to save it)

  // get the data from the text Area 
  String[] strs = split(txaSample.getText(), "\n" );

  // check if file extension (fileExtension, e.g. .txt) is there 
  int len = savePath.length(); 
  if (len<4 || !savePath.substring( len-4 ).equals(fileExtension)) {
    // file Extension is not present, we have to add it
    savePath += fileExtension; // add the file Extension
  } 
  // save 
  println("Saved: " + savePath);

  saveStrings( savePath, strs );
}

void loadIt() {
  // load
  String[] strs = loadStrings( loadPath );
  String str1 = join(strs, "\n");
  txaSample.setText(str1, 310);
}
//

// ********************************************************************************
// tab: States.pde



void drawForStateNormal() {

  background(0);

  textSize(14);

  // title 
  fill(255, 2, 2);
  text("My Little Editor", 
    width-133, 20, 
    130, 422);

  // buttons
  for (Button currentButton : listButton) {
    currentButton.display();
  } //for
  //
}
//

// End of joined file. ********************************************************************************


1 Like

The following miniEditor uses Swing components:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.io.File;

javax.swing.JFrame frame;
java.awt.Canvas canvas;

JTextArea txtArea;
JButton open;
JButton save;
JButton saveAs;
JButton clearBtn;

File file;

final int _wndW = 600;
final int _wndH = 600;

void clearAction(){
  String s = "";
  txtArea.setText(s);
}

void saveAs(){
   JFileChooser fileChooser = new JFileChooser();
            int option = fileChooser.showSaveDialog(frame);
            if(option == JFileChooser.APPROVE_OPTION){
               file = fileChooser.getSelectedFile();
               println("file = ",file);
            } else {
               println("SaveAs canceled.");
               exit();
            }
            try {          
      String content = txtArea.getText();  
      if (!file.exists()) {
        file.createNewFile();
      }
      FileWriter fw = new FileWriter(file) ;
      BufferedWriter bw = new BufferedWriter(fw);      
      bw.write(content);
      bw.close();
      } catch (IOException e) {
      //e.printStackTrace();
      JOptionPane.showMessageDialog(saveAs, e);
     }
}

void openBtn() {
  open = new JButton("Open");
  open.setBounds(20, 10, 80, 24);
  frame.add(open);
  // **** Open Action **** //
  open.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent event) {
        JFileChooser fileChooser = new JFileChooser();
        int option = fileChooser.showOpenDialog(fileChooser);
        if (option == JFileChooser.APPROVE_OPTION) {
          file = fileChooser.getSelectedFile();
          try {
            BufferedReader buffer = new BufferedReader(new FileReader(file));
            String s1 = "", s2 = "";
            while ((s1 = buffer.readLine())!= null) {
              s2 += s1 + "\n";
            }
            txtArea.setText(s2);
            buffer.close();
          } catch (Exception e) {
            JOptionPane.showMessageDialog(open,e);
          }
        }
    }
  }
  );
  open.repaint();
}

void saveBtn() {
  save = new JButton("Save");
  save.setBounds(110, 10, 80, 24);
  frame.add(save);
  // **** Save Action **** //
  save.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent event) {
        println("Save hit.", file);
        if (file != null) {
          try {
            String content = txtArea.getText();
            FileWriter fw = new FileWriter(file) ;
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(content);
            bw.close();
          } catch (IOException e) {
           // e.printStackTrace();
            JOptionPane.showMessageDialog(save,e);
          }
        } else {         
          saveAs(); // file has no name; show save dialog to give it one.
        }
    }
  }
  );
  save.repaint();
}

void saveAsBtn() {
  saveAs = new JButton("SaveAs...");
  saveAs.setBounds(200, 10, 80, 24);
  frame.add(saveAs);
     // **** SaveAs Action **** //
  saveAs.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent event) {
        println("SaveAs hit.");
        saveAs();
    }
    }
  );
  saveAs.repaint();
}

void clearBtn() {
  clearBtn = new JButton("Clear");
  clearBtn.setBounds(290, 10, 80, 24);
  frame.add(clearBtn);
     // **** Clear Action **** //
  clearBtn.addActionListener( new ActionListener() {
    void actionPerformed(ActionEvent event) {
        println("Clear hit.");
        clearAction();
    }
    }
  );
  clearBtn.repaint();
}

void textArea() {
  txtArea = new JTextArea();
  JScrollPane scrlPane = new JScrollPane(txtArea);
  scrlPane.setBounds(10, 40, _wndW-20, _wndH - 80);
  frame.add(scrlPane);
  txtArea.setEditable(true);
  // txtArea.setFont(new Font("Menlo", Font.BOLD, 16));
  txtArea.setLineWrap(true);
  txtArea.setWrapStyleWord(true);
  txtArea.repaint();
}

void buildWnd() {
  textArea();
  openBtn();
  saveBtn();
  saveAsBtn();
  clearBtn();
}

void setup() {
  frame = (javax.swing.JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame();
  canvas = (processing.awt.PSurfaceAWT.SmoothCanvas) ((processing.awt.PSurfaceAWT)surface).getNative();
  frame.setBounds(500, 300, _wndW, _wndH);
  frame.remove(canvas);
  surface.setTitle("miniEditor");
  javax.swing.SwingUtilities.invokeLater(new Runnable() {
    public void run() {
      buildWnd(); // Builds components on EventDispatchThread
    }
  }
  );
}

2 Likes

My take on the problem -

A Line based Text File Editor
User requirements

  1. The text is edited on a line by line basis
  2. When a file is selected the original is copied to make a backup
  3. The user can traverse the file forwards and backwards through the file
  4. Lines can be edited in any order.
  5. Line changes are automatically remembered
  6. User can commit all changes to the original file (overwriting it)
  7. Popup dialogs warnings to prevent user going passed beginning and end of file
  8. Popup warning before committing changes allowing original file to be unchanged

Java Swing is not used instead this sketch uses the G4P library (install from Contributions Manager).

import g4p_controls.*;

// GUI controls
GTextField txfLine; 
GButton btnFirst, btnPrev, btnNext, btnLast, btnSave, btnOpen;
GLabel lblTitle, lblGuide, lbl0, lbl1, lblLineNo, lblNbrLines; 
// File editing variables
String fname;
String[] lines;
int ln = 0, nbrLines = 0;

public void setup() {
  size(600, 300, JAVA2D);
  createGUI();
}

public void draw() {
  background(230);
}

void openFile() {
  fname = G4P.selectInput("Text file to edit", "txt", "text files");
  if (fname != null) {
    lines = loadStrings(fname);
    saveStrings(fname + ".copy", lines);
    ln = 1;
    nbrLines = lines.length;
    displayLineCounter();
    showCurrentLine();
  }
}

void saveChanges() {
  int reply = G4P.selectOption(this, "This will overwrite the original file", 
    "Do you wish to continue?", G4P.WARN_MESSAGE, G4P.OK_CANCEL);
  if (reply == G4P.CANCEL || reply == G4P.CLOSED) {
    println("Cancelled");
    return;
  }
  keepEditedLine();
  saveStrings(fname, lines);
}

void nextLine() {
  if (ln == nbrLines) {
    G4P.showMessage(this, "Currently showing last line", 
      "No next line", G4P.WARN_MESSAGE);
    return;
  }
  keepEditedLine();
  ln++;
  displayLineCounter();
  showCurrentLine();
}

void prevLine() {
  if (ln == 1) {
    G4P.showMessage(this, "Currently showing first line", 
      "No previous line", G4P.WARN_MESSAGE);
    return;
  }
  keepEditedLine();
  ln--;
  displayLineCounter();
  showCurrentLine();
}

void firstLine() {
  if (ln > 1) {
    keepEditedLine();
    ln = 1;
    displayLineCounter();
    showCurrentLine();
  }
}

void lastLine() {
  if (ln < nbrLines) {
    keepEditedLine();
    ln = nbrLines;
    displayLineCounter();
    showCurrentLine();
  }
}

void keepEditedLine() {
  lines[ln - 1] = txfLine.getText();
}

void showCurrentLine() {
  txfLine.setText(lines[ln - 1]);
}

void displayLineCounter() {
  lblLineNo.setText("" + ln);
  lblNbrLines.setText("" + nbrLines);
}

// G4P event handlers
public void gotoPrevLine(GButton source, GEvent event) { 
  prevLine();
} 

public void gotoNextLine(GButton source, GEvent event) { 
  nextLine();
} 

public void saveChanges(GButton source, GEvent event) { 
  saveChanges();
} 

public void openFile(GButton source, GEvent event) {
  openFile();
} 

public void gotoFirst(GButton source, GEvent event) {
  firstLine();
} 

public void gotoLast(GButton source, GEvent event) { 
  lastLine();
}

public void createGUI(){
  G4P.messagesEnabled(false);
  G4P.setGlobalColorScheme(GCScheme.BLUE_SCHEME);
  G4P.setMouseOverEnabled(false);
  G4P.setDisplayFont("Arial", G4P.PLAIN, 14);
  G4P.setInputFont("Arial", G4P.PLAIN, 14);
  surface.setTitle("Sketch Window");
  txfLine = new GTextField(this, 10, 140, 580, 36, G4P.SCROLLBARS_HORIZONTAL_ONLY | G4P.SCROLLBARS_AUTOHIDE);
  txfLine.setOpaque(false);
  lblTitle = new GLabel(this, 10, 10, 490, 30);
  lblTitle.setTextAlign(GAlign.CENTER, GAlign.MIDDLE);
  lblTitle.setText("Line by Line File Editor");
  lblTitle.setOpaque(true);
  btnPrev = new GButton(this, 10, 180, 110, 30);
  btnPrev.setText("Previous");
  btnPrev.addEventHandler(this, "gotoPrevLine");
  btnNext = new GButton(this, 130, 180, 110, 30);
  btnNext.setText("Next");
  btnNext.addEventHandler(this, "gotoNextLine");
  btnSave = new GButton(this, 510, 210, 80, 80);
  btnSave.setText("Save Changes");
  btnSave.setLocalColorScheme(GCScheme.RED_SCHEME);
  btnSave.addEventHandler(this, "saveChanges");
  btnOpen = new GButton(this, 510, 10, 80, 120);
  btnOpen.setText("Open File to Edit");
  btnOpen.setLocalColorScheme(GCScheme.GREEN_SCHEME);
  btnOpen.addEventHandler(this, "openFile");
  lblGuide = new GLabel(this, 10, 40, 490, 90);
  String guide = "Click on the green button to select the file to edit. ";
  guide += "A copy of the original file will be created with a '.code' extension. \n";
  guide += "You can move forward and backwards through the file making changes but ";
  guide += "these will not overwrite the original file unless you click on the ";
  guide += "'Save Changes' button.";
  lblGuide.setText(guide);
  lblGuide.setOpaque(false);
  lbl0 = new GLabel(this, 440, 180, 40, 20);
  lbl0.setTextAlign(GAlign.RIGHT, GAlign.MIDDLE);
  lbl0.setText("Line ");
  lbl0.setOpaque(false);
  lblLineNo = new GLabel(this, 480, 180, 40, 20);
  lblLineNo.setTextAlign(GAlign.CENTER, GAlign.MIDDLE);
  lblLineNo.setText("?");
  lblLineNo.setOpaque(true);
  lblNbrLines = new GLabel(this, 550, 180, 40, 20);
  lblNbrLines.setTextAlign(GAlign.CENTER, GAlign.MIDDLE);
  lblNbrLines.setText("?");
  lblNbrLines.setOpaque(true);
  lbl1 = new GLabel(this, 520, 180, 30, 20);
  lbl1.setTextAlign(GAlign.CENTER, GAlign.MIDDLE);
  lbl1.setText("of");
  lbl1.setOpaque(false);
  btnFirst = new GButton(this, 10, 220, 50, 20);
  btnFirst.setText("First");
  btnFirst.addEventHandler(this, "gotoFirst");
  btnLast = new GButton(this, 190, 220, 50, 20);
  btnLast.setText("Last");
  btnLast.addEventHandler(this, "gotoLast");
}
1 Like

…and an Editor without any libraries…

joined as one file, you may split it back into several tabs

— EDITOR —

  • This editor does work only with java (default) and not with P2D or P3D / opengl.
  • Use mouse to place cursor
  • Type normal text, use Backspace, Delete and Return
  • Use Cursor up/down, left/right
  • Use Pos1, End
  • Ctrl-Pos1, Ctrl-End
  • Pg up, Pg Down
  • ctrl-y - delete current line
  • Ctrl-1 - test text

no scrollbars


// ********************************************************************************
//         joined pde-file of folder Editor4
// ********************************************************************************


// ********************************************************************************
// tab: Editor4.pde main file (file name is folder name)



// Editor: using OWN class as Editor. NO libraries used.

// The core is the class TextBoxEditor.
//
// Demo for Editor and Save and load handling
// SEE 3 buttons.

// SKETCH EXPECTS A SUB FOLDER texts in the Sketch folder, see: final String pathFolder    = "texts";
// 
//
// from https : // forum.processing.org/two/discussion/comment/112902/#Comment_112902

// imports ---
// NONE

// classes ---

// Editor 
TextBoxEditor textBoxEditor; 

// Toolbox save / load 
ClassSaveLoadTools classSaveLoadTools = new ClassSaveLoadTools();

// list of buttons
ArrayList<Button> listButton = new ArrayList();

// consts und vars ---

// Help
final String helpText = "--- EDITOR ---\n"
  +"This editor does work only with java (default) and not with P2D or P3D / opengl.\n" 
  +"Use mouse to place cursor\nType text, use Backspace, Delete and Return\nUse Cursor up/down, left/right\nPos1, End\nCtrl-Pos1, Ctrl-End\nPg up, Pg Down\nctrl-y - delete current line\nCtrl-1 - test text";

// states of the program:
final int normal = 0; // unique constants
final int save   = 1;
final int load   = 2;
///current state (must be one of them) 
int state=normal;

// ------------------------------------------------
// Core functions of processing 

void setup() {
  size(1800, 900);

  this.registerMethod("keyEvent", this);

  println(helpText.replace("\n", "\n * "));

  // Create a text area (without horizontal and 
  // vertical scrollbars)
  final String textToStart = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.\n"
    +"Aenean commodo ligula eget dolor.\nAenean massa.\nCum sociis natoque penatibus et magnis\ndis parturient montes,\nnascetur ridiculus mus"; 
  textBoxEditor = new TextBoxEditor( textToStart, 
    20, 20, // x, y
    width-200, height-60, // width, height of the textBoxEditor
    0300 << 030, color(255), // textColor, backgroundColor
    color(-1, 0100), color(#FF00FF, 0200)); // bordColor, slctColor

  // init buttons
  String[] buttonTitles = {"Save", "Load", "New" };
  int i=0;
  for (String buttonTitle : buttonTitles) {
    listButton.add(new Button (buttonTitle, width-70, 60+i*40, 50, 25));
    i++;
  }//for
}//func 

void draw() {
  switch (state) {

  case normal:
    //normal
    drawForStateNormal() ;
    break; 

  case save:
    // wait for Save Dialog 
    classSaveLoadTools.waitForSaveDialog();
    break; 

  case load:
    // wait for Load Dialog 
    classSaveLoadTools.waitForLoadDialog();
    break;

  default:
    //Error 
    println("Error: 4777");
    exit();
    break; 
    //
  }//switch
  //
}//func
//

// ********************************************************************************
// tab: classButton.pde



class Button {

  String textButton; 
  PVector position = new PVector();
  float sizeW, sizeH; 

  // constructor
  Button (  String s1_, 
    float x_, float y_, 
    float sizeW_, float sizeH_ ) {

    position.set(x_, y_);

    sizeW = sizeW_;
    sizeH = sizeH_;

    textButton=s1_;
  }// constructor

  // ------------------------------------------
  // methods

  void display() {
    // show button 

    // rect
    stroke(200);
    fill(255); 
    if (checkMouse())
      fill(255, 0, 0); 
    if (checkMouse()&&mousePressed)
      fill(255, 0, 120); 
    rect(position.x, position.y, 
      sizeW, sizeH);

    // text 
    fill(0);
    // text is centered
    textAlign(CENTER); 
    text(textButton, 
      position.x+sizeW/2, position.y+17);
    //reset 
    textAlign(LEFT);
  }// method

  boolean checkMouse() {
    // returns true when inside
    return
      mouseX > position.x &&
      mouseX < position.x + sizeW &&
      mouseY > position.y &&
      mouseY < position.y + sizeH;
  } // method
  //
}//class
//

// ********************************************************************************
// tab: classSaveLoad.pde



// Save and load

// the two 'callback' functions MUST BE OUTSIDE THE CLASS

// 3 blocks with 2 functions each, for save and load 

class ClassSaveLoadTools {

  // editor path and file extension
  final String pathFolder    = "texts";
  final String fileExtension = ".txt";

  // Paths (returned by 'callback' functions after using save and load)
  String savePath=""; 
  String loadPath=""; 

  // no constr

  // --------------------------------------------------------------------
  // the two init functions

  void initSave() {
    // init save process 
    // reset
    savePath="";
    // make date time stamp (the expression nf(n,2) means leading zero: 2 becomes 02)
    String dateTimeStamp = year() 
      + nf(month(), 2) 
      + nf(day(), 2) 
      + "-" 
      + nf(hour(), 2)
      + nf(minute(), 2)
      + nf(second(), 2);
    // prepare fileDescription which occurs in the dialogue
    File fileDescription = new File( sketchPath()
      + "//"
      + pathFolder 
      + "//" 
      + dateTimeStamp
      + fileExtension);
    // open the dialog  
    selectOutput("Select a file to write to", "fileSelectedSave", fileDescription);
    // set state to wait
    state=save;
  }

  void initLoad() {
    // init load process 
    // reset
    loadPath="";
    // prepare fileDescription which occurs in the dialogue
    File fileDescription = new File( sketchPath()+"//"+pathFolder+"//"+"*" + fileExtension );
    // open the dialog
    selectInput("Select a file to load", "fileSelectedLoad", fileDescription);
    // set state to wait
    state=load;
  }

  // ----------------------------------------------------
  // waiting 

  void waitForSaveDialog() { 
    if (!savePath.equals("")) {
      // waiting is over
      saveIt();
      // go back 
      state=normal;
    }
  }

  void waitForLoadDialog() { 
    if (!loadPath.equals("")) {
      // waiting is over
      loadIt();
      // go back 
      state=normal;
    }
  }

  // ----------------------------------------------------
  // executing save and load

  void saveIt() {
    // save
    // split at line break and make array (to save it)

    // get the data from the text Area 
    String[] strs = split(textBoxEditor.getText(), "\n" );

    // check if file extension (fileExtension, e.g. .txt) is there 
    int len = savePath.length(); 
    if (len<4 || !savePath.substring( len-4 ).equals(fileExtension)) {
      // file Extension is not present, we have to add it
      savePath += fileExtension; // add the file Extension
    } 
    // save 
    println("Saved: " + savePath);

    saveStrings( savePath, strs );
  }

  void loadIt() {
    // load
    String[] strs = loadStrings( loadPath );
    String str1 = join(strs, "\n");
    textBoxEditor.setText(str1);
  }
  //
}//class
//

// ********************************************************************************
// tab: classTextBoxEditor.pde



// Editor 

class TextBoxEditor {

  // debug
  String testText = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. "
    +"Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a. "+
    "venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, "
    +"eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur "
    +"ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus. sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, "
    +"hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit "
    +"amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc";

  boolean ctrlIsDown=false;

  color textColor, textBoxBackgroundColor, 
    borderColor, // not in use  
    selectedColor; // not in use  
  int x, y, 
    w, h;

  // this is the entire text of the editor text area 
  String[] editorArray = new String[220];

  // whether the blinking line is currently on or off 
  boolean showCursorAsLine=true; 

  // for cursor movements: 
  // When we use CRS up and down currentLine changes; 
  // for CRS left and right currentColumn changes.  
  int currentColumn = 0; // x-value measured in characters
  int currentLine   = 0; // y-value measured in lines 

  // when working within one line by CRS left and right, by backspace and by delete,
  // the line is internally split up into 2 strings, which are left and right from the 
  // cursor. Thus it's easy to do backspace / delete within a line etc. 
  // See initNewLine() and others. 
  String leftText="", rightText="";

  // ------------------------------------------------------

  // constr 
  TextBoxEditor(String textIn_, 
    int xxIn_, int yyIn_, 
    int wwIn_, int hhIn_, 
    color textColorIn_, color textBoxBackgroundColorIn_, 
    color bordColorIn_, color slctColorIn_) {  // not in use  

    x =  xxIn_;
    y =  yyIn_;
    w =  wwIn_;
    h =  hhIn_;

    textColor = textColorIn_;

    textBoxBackgroundColor = textBoxBackgroundColorIn_;

    // not in use  
    borderColor = bordColorIn_;
    selectedColor = slctColorIn_;

    setText(textIn_); 

    PGraphics dummy = new PGraphics();  
    // int keyCodePos1; int keyCodeEnd; int keyCodePageUp; int keyCodePageDown; 
    if (dummy.is3D()) {
      // BAD 
      println("This editor does work only with java (default) and not with P2D or P3D / opengl. ");
      exit();
    } else {
      //Good
    }//else
  } // constr 

  // ------------------------------------------------------

  void setText(String text1_) {
    editorArray = split(text1_, "\n");
    currentLine=0;
    initNewLine();
  } // func 

  String getText() {
    return 
      join(editorArray, "\n");
  } // func

  // ------------------------------------------------------

  void display() {
    // display Editor text box  

    // draw box 
    rectMode(CORNER);
    noStroke(); 
    fill(textBoxBackgroundColor);
    rect(x, y, w, h);

    // draw lines 
    textSize(14);
    fill(textColor);

    float textx=x+3;
    float texty=y+2; 

    for (int i = 0; i < editorArray.length; i++) {

      if (i==currentLine) {
        // this is the current line of the cursor
        text(leftText + rightText, 
          textx, texty, 
          w, h);
        blinkingCursor(textx, texty);
      } else {
        // other lines
        text(editorArray[i], 
          textx, texty, 
          w, h);
      }
      //next line
      texty+=13;
    }//for
    //
  }//method

  // ------------------------------------------------------
  // Inputs Keyboard and mouse 

  void keyPressedInClassEditor() {
    if (key==CODED) {
      // Coded
      checkCodedKeys();
    } else {
      // NOT Coded
      checkNotCodedKeys();
    } // NOT Coded
    //
  } //func

  void keyReleasedInClassEditor() {

    final int keyCodeCtrl=17;

    if (keyCode==keyCodeCtrl) {
      ctrlIsDown=false;
    }//if
  } // func 

  void mouseInClassEditor() {
    // use mouse to set cursor to a position 

    if (! overEditor() )
      return; 

    textSize(14);

    boolean done1=false; 
    boolean done2=false; 

    //currentColumn = 0;
    //currentLine = 0;

    // find currentLine of cursor position 
    int texty = y+2;    
    for (int i = 0; i < editorArray.length; i++) {
      if (mouseY>texty &&
        mouseY<texty+13) { 
        currentLine = i;
        if (currentLine<0)
          currentLine=0;
        done1 = true; 
        break;
      }//if
      //next line
      texty+=13;
    }//for

    if (! done1) // something went wrong
      return; 

    // find currentColumn of cursor position
    String currentText=""; 
    for (int i = 0; i < editorArray[currentLine].length(); i++) {
      // we check the width of the text letter by letter 
      float leftWidth = x+textWidth(currentText);
      currentText += editorArray[currentLine].charAt(i);
      float newWidth = x+textWidth(currentText);
      if (mouseX > leftWidth &&
        mouseX < newWidth) {
        currentColumn = i;
        initNewLine(); // currentColumn
        done2=true; 
        break;
      }//if
    }//for

    // when we didn't find a place to insert, we assume the end of the line
    if (! done2) {
      currentColumn = editorArray[currentLine].length();
      initNewLine(); // currentColumn
    }//if
    //
  }//method 

  // -----------------------------------------------------------------
  // smaller funcs for keyboard 

  void checkNotCodedKeys() {
    // not coded......

    if (ctrlIsDown) {
      // with control
      if (key == '1') {
        // Ctrl-1
        testText=testText.replace(". ", ".\n"); 
        setText(testText);
      } else if (keyCode == 'Y') {
        // Ctrl-y
        deleteLine();
      } else {
        //
      }
      //
    } else {
      // without control
      if (key == BACKSPACE) {  
        if (leftText.length()>0) {
          leftText = leftText.substring(0, leftText.length()-1);
          currentColumn--;
          writeLineBackInArray();
        } else {
          // we are at start of line and with Backspace we move the entire current line to the end of the last line
          currentColumn=0;
          if (currentLine==0)
            return;
          int prevLengthPrevLine=editorArray[currentLine-1].length(); 
          editorArray[currentLine-1]+=editorArray[currentLine];
          deleteLine();
          currentColumn = prevLengthPrevLine;
          currentLine--;
          initNewLine();
        }
      } else if (key == ENTER || key == RETURN) {
        doReturnKey();
      } else if (key == TAB) {
        leftText += "    ";
        currentColumn+=4;
        writeLineBackInArray();
      } else if (key == DELETE) {
        doDeleteKey();
      } else if (key >= ' ') {
        // Normal typing of text 
        leftText += str(key);
        currentColumn++;
        writeLineBackInArray();
      }
      // ----
    }//else without CTRL
  }//method

  void checkCodedKeys() {
    // Coded
    // CODED from now on.....

    if (ctrlIsDown) {
      // with control key

      if (keyCode==36) {
        // "Ctrl-Pos1"
        writeLineBackInArray();
        if (currentLine!=0)
          currentLine=0;
        else currentColumn=0; 
        initNewLine();
      } else if (keyCode==35) {
        // "Ctrl-End"
        writeLineBackInArray();
        if (currentLine>editorArray.length-1)
          currentLine=editorArray.length-1;
        // If we are not in last line
        if (currentLine!=editorArray.length-1) {
          currentLine=editorArray.length-1; // set current line to last line
        } else {
          currentColumn=editorArray[currentLine].length(); // if we are there already, set the column to end of line
        }
        initNewLine();
      } else if (keyCode==33) {
        //text1 = "Ctrl-Pg Up";
      } else if (keyCode==34) {
        //text1 = "Ctrl-Pg Down";
      }
    } else {
      // without control key

      if (keyCode == LEFT) {
        decreaseCurrentColumn();
      } else if (keyCode == RIGHT) {
        increaseCurrentColumn();
      } else if (keyCode == UP) {
        // previous line in text 
        writeLineBackInArray(); 
        currentLine--;
        if (currentLine<0)
          currentLine=0;
        initNewLine(); // currentColumn
      } else if (keyCode == DOWN) {
        // next line in text 
        writeLineBackInArray();    
        currentLine++;
        if (currentLine>editorArray.length-1)
          currentLine=editorArray.length-1;
        initNewLine(); // currentColumn
      }
      //---
      else if (keyCode==36) {
        // text1 = "Pos1";
        currentColumn=0;
        initNewLine();
      } else if (keyCode==35) {
        // text1 = "End";
        currentColumn=editorArray[currentLine].length();
        initNewLine();
      } else if (keyCode==33) {
        // text1 = "Pg Up";
        currentLine-=4;
        if (currentLine<0) 
          currentLine=0; 
        initNewLine();
      } else if (keyCode==34) {
        //  text1 = "Pg Down";
        currentLine+=4;
        if (currentLine>editorArray.length-1) 
          currentLine=editorArray.length-1; 
        initNewLine();
      }
      //
    }//else without control key
  }//func 

  void doReturnKey() {
    editorArray[currentLine]=leftText; 
    String[] newLineArray=new String[1];
    newLineArray[0] = rightText; 
    // Splice one array of values into another
    editorArray = splice(editorArray, newLineArray, currentLine+1);
    currentLine++;
    currentColumn = 0;
    initNewLine();
  }

  void doDeleteKey() {
    // Do delete key 
    if (rightText.length()-1>=0) {
      // Delete char right of the cursor (normal situation)
      rightText = rightText.substring(1); // remove 1st char in rightText 
      writeLineBackInArray();
    } else {
      // 
      writeLineBackInArray();
      // rightText += editorArray[currentLine+1];
      int prevLengthPrevLine=editorArray[currentLine].length();
      if (editorArray.length<=1) 
        return; 
      if (currentLine+1>=editorArray.length) 
        return; 
      editorArray[currentLine] += editorArray[currentLine+1];
      //  writeLineBackInArray();
      currentLine++;
      deleteLine();
      writeLineBackInArray();
      currentLine--;
      currentColumn=prevLengthPrevLine; 

      initNewLine(); 
      // writeLineBackInArray();
    }//else 
    //
  }//method 

  // -------------------------------------------------------------------

  boolean overEditor() {
    if (mouseX >= x &&
      mouseX <= x+w &&
      mouseY >= y && 
      mouseY <= y+h) {
      return true;
    } else {
      return false;
    }
  } // method

  void spliceNewTextIntoEditor (String[] newArray) {
    // Splice one array of values into another.
    // (This can be used when we load a second file and insert it in the editor at cursor position.)  
    // Not in use. 
    editorArray = splice(editorArray, newArray, currentLine);
  } // method

  void initNewLine() {
    // when entering a new line 
    // we set left and right String leftText and rightText

    // when column # too small, fix it 
    if (currentColumn<0) {
      currentColumn=0;
    }
    // when no text is present, we make an empty text
    if (editorArray.length<=0) {
      editorArray=new String[1];
      editorArray[0]="";
    }

    //if line number is too high, fix it
    if (currentLine>editorArray.length-1)
      currentLine=editorArray.length-1; 

    // column is too low, fix it
    if (currentLine<0)
      currentLine=0; 
    if (currentColumn > editorArray[currentLine].length()) {
      currentColumn=editorArray[currentLine].length(); //-1? ???
      if (currentColumn<0)
        currentColumn=0;
    }

    // set left and right String
    leftText  = editorArray[currentLine].substring( 0, currentColumn); 
    rightText = editorArray[currentLine].substring( currentColumn );
    //
  } // method

  void writeLineBackInArray() {
    // when leaving a line, the leftText + rightText needs to go back in the array

    editorArray[currentLine] = leftText + rightText;
  } // method

  void decreaseCurrentColumn() {
    currentColumn--;
    if (currentColumn<0)
      currentColumn=0;
    initNewLine();
  } // method

  void increaseCurrentColumn() {
    currentColumn++;
    if (currentColumn>editorArray[currentLine].length())
      currentColumn=editorArray[currentLine].length();
    // initNewLine();
    leftText  = editorArray[currentLine].substring( 0, currentColumn); 
    rightText = editorArray[currentLine].substring( currentColumn );
  } // method

  void blinkingCursor( float textx, float texty) {

    if ((frameCount%11) == 0) {
      showCursorAsLine= ! showCursorAsLine;
    }

    if (showCursorAsLine) {
      float leftWidth = textWidth(leftText);
      stroke(255, 0, 0); 
      strokeWeight(1); 
      line(textx+leftWidth, texty, 
        textx+leftWidth, texty+19);
      stroke(0);
    }
  } // method

  void newText() {
    // New
    // editorArray = split(text1, "\n");
    editorArray = split("Hello", "\n");
    currentLine=0;
    currentColumn=0;
    initNewLine();
  } // method

  void deleteLine() {
    // delete line
    writeLineBackInArray(); 
    String[] before = (String[]) subset(editorArray, 0, currentLine);
    String[] after  = (String[]) subset(editorArray, currentLine+1);

    editorArray =  (String[]) concat(before, after); 
    initNewLine();
  } // method
  //
}//class 
//

// ********************************************************************************
// tab: InputsKeys.pde


// Inputs Keyboard  

void keyPressed() {

  switch (state) {

  case normal:
    if (key==ESC) {
      key=0; // kill
    }
    // editor codes !
    textBoxEditor.keyPressedInClassEditor(); 
    break; 

  case save:
  case load:
    // ignore
    break;

  default:
    // Error
    println("Error 975: "+"unknown state "+(state));
    exit(); 
    break;
  }//switch 
  //
} //func

public void keyEvent(KeyEvent ke) {
  if (ke.getAction() == KeyEvent.PRESS) {    
    textBoxEditor.ctrlIsDown = (ke.getModifiers() & KeyEvent.CTRL) == KeyEvent.CTRL;
  }
}

void keyReleased() {
  if (state!=normal)
    return;

  // editor codes !
  textBoxEditor.keyReleasedInClassEditor();
} // func 

// ********************************************************************************
// tab: InputsMouse1.pde


// Inputs mouse

void mousePressed() {

  if (state!=normal)
    return;

  String command=""; 

  // for the buttons 
  for (Button currentButton : listButton) {
    if (currentButton.checkMouse()) {
      command=currentButton.textButton; 
      break;
    }//if
  } //for

  // found a mouse button like save/load/new 
  if (! command.equals("")) {
    // execute 
    doCommand(command);
  } else {
    // editor : 
    textBoxEditor.mouseInClassEditor();
  }//else editor
  //
} //func

// --------------------------------------------------------------------------------

void doCommand(String command) {
  //
  if (command.equals("")) 
    return;

  switch(command) {
  case "Load":
    classSaveLoadTools.initLoad();
    break;

  case "Save":
    classSaveLoadTools.initSave();
    break;

  case "New":
    textBoxEditor.newText();
    break;

  default:
    // Error 
    println("unknown command "+command);
    exit();    
    break;
  }//switch 
  //
} //func
//

// ********************************************************************************
// tab: States.pde


// The states of the program

void drawForStateNormal() {

  background(0);

  // title 
  fill(255, 2, 2);
  textSize(14);
  text("Little Editor", 
    width-133, 20, 
    130, 422);

  // buttons
  for (Button currentButton : listButton) {
    currentButton.display();
  } //for

  // show Editor Text Box 
  textBoxEditor.display();
  textSize(24);
  //
}
//

// ********************************************************************************
// tab: ToolsSaveLoad.pde


// the two 'callback' functions MUST BE OUTSIDE THE CLASS
// this tab is an addition to the class

void fileSelectedSave(File selection) {
  // the 'callback' function
  if (selection == null) {
    // Window was closed or the user hit cancel
    // go back 
    state=normal;
  } else {
    // User selected  selection.getAbsolutePath()
    classSaveLoadTools.savePath=selection.getAbsolutePath();
  }
}

void fileSelectedLoad(File selection) {
  // the 'callback' function
  if (selection == null) {
    // Window was closed or the user hit cancel
    // go back 
    state=normal;
  } else {
    // User selected selection.getAbsolutePath()
    classSaveLoadTools.loadPath=selection.getAbsolutePath();
  }
}
//

// End of joined file. ********************************************************************************
1 Like

@Chrisir

This is the sample code I was looking for.
Thank you.
Very nice source code.

1 Like