Python/Py5Editor

The following source code will create an entry level Python/Py5 Editor which will run on all three platforms (initially developed for MacOS). It was written using Swing components with RSyntaxTextArea used for syntax highlighting. Setup is non-trivial and probably more suited to advanced programmers. Thonny is likely a better choice for beginners.

Before running, RSyntaxTextArea.jar will need to be downloaded from here:Download rsyntaxtextarea_1.5.1.zip (RSyntaxTextArea) and added to your Processing’s ‘libraries’ folder as described here: RSyntaxTextArea With Swing In Processing.

The second chore is to install py5 following instructions found on the py5 website: https://py5coding.org. An ‘environment’ setup is helpful on MacOS and Linux; I’m not sure how beneficial it is for Windows. I used ‘Miniconda3’(instructions also available on net), but a more robust ‘Anaconda’ or ‘venv’ environment is also possible.

Py5 source code may be run in two of the five modes, imported and module, described on the the website. “Module mode” also works for pure Python code (eg, TKinter, Numpy, MatPlotLib, Shapely, Trimesh, GraphWin, Pyglet, PyObjc, etc).

The editor uses ProcessBuilder to ‘run’ the source code and requires two parameters: a ‘cmdStr’ and a ‘filePath’. The ‘cmdStr’ for ‘imported’ requires the url to an executable file entitled py5-run-sketch and the ‘module’ mode requires the url to a python or python3 executable. On macOS ‘py5-run-sketch.exe’ looks like that shown below and is found at ‘/opt/miniconda3/bin’ on my system; ‘python3’ executable is located in the same folder. With Windows 11 those two executables are in different locations:“C:\Users\s\AppData\Local\Programs\Python\Python313\Scripts\py5-run-sketch.exe” for ‘py5-run-sketch’ and at “C:\Users\s\AppData\Local\Programs\Python\Python313\Python.exe” for ‘python’. Obviously these locations could be different on your system depending on how you have set it up. The main thing is that your code has to point to the the py5-run-sketch and python(3) executables on your system.

Before running the demo you will need to set ‘folderStr’ for the default folder you want the chooser to open and the ‘cmdStr’ for each mode based on your operating system. Below are a few simple demos which you may use to check each mode.

After py5 has been installed if the editor returns a ‘No module’ exception for code that you are trying to run then try using pip(3) install missingModule from the command line (Terminal on macOS); if the module is available it will be downloaded to your system and immediately available to your code.

The auxiliary window in the editor is for comparing code or temporarily parking code for copy/paste procedures; visibility is toggled by the checkbox. The editor is not document based, ie, you can only have one instance of it open at any one time, hence the need for an auxiliary window if you want to have two sketches visible simultaneously.

The editor source code may be exported by Processing into a stand-alone app. With macOS the app icon can be changed to whatever image you wish by right clicking on the icon, selecting ‘Get Info’ and then drag and dropping your image onto the app icon in the upper left corner. You should see a “+” sign when the app accepts the new icon. I have not tried changing the exported Processing app icon on the other two platforms.

The editor is versatile enough that it could be modified to build and run source code for other languages; RSyntaxTextArea reportedly supports about 40 languages.

Why python?

Python prides itself in being simple compared to some other languages. For example instead of: JButton myBtn = new JButton(); you can write: myBtn = JButton() and forget the semicolon.

Another advantage is that there are no curly braces for code blocks; indentation is used instead, usually four spaces per block (two spaces also works).

Py5 allows us to use Processing-like syntax in Python which is a big advantage and decreases the learning curve. The addition of ‘imported mode’ means that you don’t have to use a bunch of ‘py5.’ prefixes like in ‘module mode’ and you have access to JavaFX with all of it’s nice controls and graphics with high resolution. In order to get JavaFX to work I found py5_tools.processing.download_library('JavaFX') to be helpful. Swing and Control P5 components are also supported. It’s a nice way to write code once you get past the setup.

Source code with 5 separate tabs:


THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHTHOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THESOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

// **** Describes how to use RSyntaxTextArea for syntax highlighting **** //
//https://discourse.processing.org/t/rsyntaxtextarea-with-swing-in-processing/44725
// **** Download jar file for rsyntaxtextarea **** //
//https://sourceforge.net/projects/rsyntaxtextarea/files%2Frsyntaxtextarea%2F1.5.1%2Frsyntaxtextarea_1.5.1.zip/download

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

import org.fife.ui.rsyntaxtextarea.*;
import org.fife.ui.rtextarea.*;

import java.io.*;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.swing.text.BadLocationException;
import static java.awt.event.InputEvent.META_DOWN_MASK;

import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseListener;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import java.util.ArrayList;
import java.util.List;

JFrame frame;
JScrollPane txtScrlPane;
RSyntaxTextArea txtArea;
RSyntaxTextArea auxTextArea;
JTextArea logArea;
JSplitPane splitPane;
JScrollPane logScrlPane;
JScrollPane tbarScrlPane;
JToolBar toolbar;
JButton fileBtn;
JPopupMenu pupMenu;

JMenu openRecentMenu;
List<String> recentFiles = new ArrayList<>();

JLabel folderSelectionLabel;
JRadioButton editBtn;
JFrame aux;

File file;
Process process = null;

final int _wndW = 770;
final int _wndH = 700;

// **** Change folderStr and cmdStr for your system **** //
// **** Two command strings, one for each mode: Lines 474,478,480 **** //
String folderStr = "/Users/s/py5_code";
String cmdStr = "";
String filePath = "";
File dirFile;

void openFile(String selectedFilePath) {
  frame.setTitle(selectedFilePath);
  filePath = selectedFilePath; //update filePath
  Path filePath2 = Paths.get(filePath);
  Path dirPath = filePath2.getParent();
  dirFile = dirPath.toFile();
  txtArea.setText("");
  try {
    BufferedReader buffer = new BufferedReader(new FileReader(selectedFilePath));
    String s = "", s1 = "";
    while ((s = buffer.readLine())!= null) {
      s1 += s + "\n";
    }
    txtArea.setText(s1);
    buffer.close();
    editBtn.setSelected(false);
  }
  catch (Exception ex) {
    logArea.append(ex + "\n");
  }
}

void updateOpenRecentMenu() {
  openRecentMenu.removeAll();
  if (recentFiles.isEmpty()) {
    JMenuItem noRecentItem = new JMenuItem("No Recent Files");
    noRecentItem.setEnabled(false); // Disable if no recent files
    openRecentMenu.add(noRecentItem);
  } else {
    for (String filePath : recentFiles) {
      JMenuItem recentFileItem = new JMenuItem(filePath);
      recentFileItem.addActionListener(new ActionListener() {
        @Override
          public void actionPerformed(ActionEvent e) {
          openFile(filePath);
        }
      }
      );
      openRecentMenu.add(recentFileItem);
    }
  }
}

void addRecentFile(String filePath) {
  if (!recentFiles.contains(filePath)) {
    recentFiles.add(0, filePath); // Add to the beginning
    if (recentFiles.size() > 10) { // Limit to 10 recent files
      recentFiles.remove(recentFiles.size() - 1);
    }
  }
}

String fileExtension(String fileName) {
  int lastDotIndex = fileName.lastIndexOf('.');
  if (lastDotIndex == -1 || lastDotIndex == 0) {
    return ""; // No extension
  }
  return fileName.substring(lastDotIndex + 1);
}

void openAction() {
  JFileChooser fileChooser = new JFileChooser(folderStr);
  int i = fileChooser.showOpenDialog(fileChooser);
  if (i == JFileChooser.APPROVE_OPTION) {
    file = fileChooser.getSelectedFile();
    filePath = file.getPath();
    if (!fileExtension(filePath).equals("py")) {
      Object[] options = {"CANCEL" };
      JOptionPane.showOptionDialog(frame, "Please make another selection.", "Editor requires .py extension.",
        JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
        return;
    }
    dirFile = fileChooser.getCurrentDirectory();
    addRecentFile(filePath);
    updateOpenRecentMenu();
    frame.setTitle(filePath);
    try {
      BufferedReader buffer = new BufferedReader(new FileReader(filePath));
      String s = "", s1 = "";
      while ((s = buffer.readLine())!= null) {
        s1 += s + "\n";
      }
      txtArea.setText(s1);
      buffer.close();
      editBtn.setSelected(false);
    }
    catch (Exception ex) {
      logArea.append(ex + "\n");
    }
  } else {
    logArea.append("Open cancelled.");
  }
}

void saveAction() {
  if (filePath != null) {
    try {
      String content = txtArea.getText();
      FileWriter fw = new FileWriter(filePath) ;
      BufferedWriter bw = new BufferedWriter(fw);
      bw.write(content);
      bw.close();
      editBtn.setSelected(false);
    }
    catch (IOException e) {
      logArea.append(e + "\n");
    }
  } else {
    saveAsAction();
  }
}

void saveAsAction() {
  JFileChooser fileChooser = new JFileChooser(folderStr);
  fileChooser.setSelectedFile(new File("untitled.py"));
  int option = fileChooser.showSaveDialog(frame);
  if (option == JFileChooser.APPROVE_OPTION) {
    file = fileChooser.getSelectedFile();
    filePath = file.getPath();
    frame.setTitle(filePath);
    if (file == null) {
      return;
    }
  } else {
    logArea.append("SaveAs cancelled.");
  }
  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();
    frame.setTitle(filePath);
    editBtn.setSelected(false);
  }
  catch (IOException e) {
    logArea.append(e + "\n");
  }
}

// ===== Sets default folder to open ===== //
void folderSelectionAction() {
  JFileChooser chooser = new JFileChooser();
  chooser.setDialogTitle("Select folder:");
  chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
  // chooser.setAcceptAllFileFilterUsed(false);
  if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
    file = chooser.getSelectedFile();
    File dir = chooser.getCurrentDirectory();
    folderStr = dir + "/" + file.getName();
    folderSelectionLabel.setText(dir + "/" + file.getName());
  } else {
    folderSelectionLabel.setText("No selection.");
  }
}

void runAction() {
  ProcessBuilder processBuilder = new ProcessBuilder(cmdStr, filePath);
  processBuilder.environment().put("PYTHONUNBUFFERED", "TRUE");
  processBuilder.directory(dirFile);
  try {
    process = processBuilder.start();
    BufferedReader stdIn = new BufferedReader(new InputStreamReader(process.getInputStream()));
    BufferedReader stdErr = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    String outStr = "";
    while ((outStr = stdIn.readLine()) != null) {
      logArea.append(outStr + "\n");
    }
    String errStr = "";
    while ((errStr = stdErr.readLine()) != null) {
      logArea.append(errStr + "\n");
    }
    // Wait for the process to complete and get its exit value
    int exitCode = process.waitFor();
    logArea.append("\nProcess exited with code: " + exitCode + "\n");
  }
  catch (IOException | InterruptedException e) {
    logArea.append(e + "\n");
  }
}

void runBtnAction() {
  if (editBtn.isSelected()) {
    saveAction();
  }
  thread("runAction");
}

void stopBtnAction() {
  process.destroy();
}

void clearAllAction() {
  txtArea.setText("");
  logArea.setText("");
  frame.setTitle("");
  editBtn.setSelected(false);
}

void clearLogAction() {
  logArea.setText("");
}

void auxOpenAction() {
  JFileChooser fileChooser = new JFileChooser(folderStr);
  int i = fileChooser.showOpenDialog(fileChooser);
  if (i == JFileChooser.APPROVE_OPTION) {
    File auxFile = fileChooser.getSelectedFile();
    String auxFilePath = auxFile.getPath();
    aux.setTitle(auxFilePath);
    try {
      BufferedReader buffer = new BufferedReader(new FileReader(auxFilePath));
      String s = "", s1 = "";
      while ((s = buffer.readLine())!= null) {
        s1 += s + "\n";
      }
      auxTextArea.setText(s1);
      buffer.close();
    }
    catch (Exception ex) {
      auxTextArea.append(ex + "\n");
    }
  } else {
    auxTextArea.append("Open cancelled.");
  }
}

void auxWndAction() {
  aux = new JFrame();
  aux.setBounds(100, 100, 600, 400);
  aux.setResizable(true);
  aux.setVisible(true);
  auxTextArea = new RSyntaxTextArea();
  auxTextArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PYTHON);
  auxTextArea.setFont(new Font("Menlo", Font.PLAIN, 14));
  RTextScrollPane auxTextScrlPane = new RTextScrollPane(auxTextArea);
  auxTextArea.setEditable(true);
  auxTextArea.setLineWrap(false);
  auxTextArea.setWrapStyleWord(true);
  aux.add(auxTextScrlPane);
  auxTextArea.repaint();
  // ==== Aux Toolbar ====== //
  JToolBar auxTbar = new JToolBar();
  auxTbar.setFloatable(false);
  auxTbar.setPreferredSize(new Dimension(_wndW-20, 40));
  aux.add(auxTbar, BorderLayout.PAGE_START);
  // ====== Aux Open Button ===== //
  JButton auxOpenBtn = new JButton("Open file:");
  auxTbar.add(auxOpenBtn);
  auxOpenBtn.addActionListener(e-> {
    auxOpenAction();
  }
  );
}

void quitBtnAction() {
  exit();
}

void toolBar() {
  toolbar = new JToolBar();
  toolbar.setLayout(null);
  toolbar.setFloatable(false);
  JScrollPane tbarScrlPane = new JScrollPane(toolbar);
  toolbar.setPreferredSize(new Dimension(_wndW-20, 50));
  frame.add(tbarScrlPane, BorderLayout.PAGE_START);
}

void txtArea() {
  txtArea = new RSyntaxTextArea();
  txtArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_PYTHON);
  txtArea.setFont(new Font("Menlo", Font.PLAIN, 14));
  txtScrlPane = new RTextScrollPane(txtArea);
  txtArea.setEditable(true);
  txtArea.setLineWrap(false);
  txtArea.setWrapStyleWord(true);
  txtArea.repaint();

  txtArea.getDocument().addDocumentListener(new DocumentListener() {
    @Override
      public void insertUpdate(DocumentEvent e) {
      editBtn.setSelected(true);
    }
    @Override
      public void removeUpdate(DocumentEvent e) {
      editBtn.setSelected(true);
    }
    @Override
      public void changedUpdate(DocumentEvent e) {
      // For attribute changes, not text changes
    }
  }
  );

  frame.add(txtScrlPane);
}

void logArea() {
  logArea = new JTextArea();
  logScrlPane = new JScrollPane(logArea);
  logArea.setEditable(true);
  logArea.setFont(new Font("Menlo", Font.PLAIN, 12));
  logArea.setLineWrap(false);
  logArea.setWrapStyleWord(true);
  logArea.repaint();
}

//Create a split pane with the two scroll panes in it.
void splitPane() {
  splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, txtScrlPane, logScrlPane);
  splitPane.setBounds(0, 0, _wndW, _wndH - 30);
  splitPane.setOneTouchExpandable(true);
  frame.add(splitPane);
  splitPane.setDividerLocation(_wndH - 210);
  splitPane.repaint();
  //Provide minimum sizes for the two components in the split pane
  Dimension minimumSize = new Dimension(_wndW, 50);
  txtScrlPane.setMinimumSize(minimumSize);
  logScrlPane.setMinimumSize(minimumSize);
}

void popUpMenu() {
  pupMenu = new JPopupMenu();
  fileBtn = new JButton("File");
  fileBtn.setBounds(10, 10, 60, 24);
  fileBtn.setToolTipText("File menu.");
  toolbar.add(fileBtn);
  fileBtn.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      String s = e.getActionCommand();
      if (s.equals("File")) {
        // add the popup to the frame
        pupMenu.show(toolbar, 10, 35);
      }
    }
  }
  );
  // ==== Open ==== //
  JMenuItem mItemOpen = new JMenuItem("Open...");
  mItemOpen.addActionListener(e-> {
    openAction();
  }
  );
  pupMenu.add(mItemOpen);

  // ==== Open Recent ==== //
  openRecentMenu = new JMenu("Open Recent");
  pupMenu.add(openRecentMenu);

  // ==== Save ==== //
  JMenuItem mItemSave = new JMenuItem("Save");
  mItemSave.addActionListener(e-> {
    saveAction();
  }
  );
  mItemSave.setAccelerator(KeyStroke.getKeyStroke('S', META_DOWN_MASK));
  pupMenu.add(mItemSave);

  // ==== SaveAs ==== //
  JMenuItem mItemSaveAs = new JMenuItem("SaveAs...");
  mItemSaveAs.addActionListener(e-> {
    saveAsAction();
  }
  );
  pupMenu.add(mItemSaveAs);
}

void folderSelectionLabel() {
  folderSelectionLabel = new JLabel();
  folderSelectionLabel.setHorizontalAlignment(JLabel.LEFT);
  folderSelectionLabel.setBounds(85, 25, 320, 24);
  folderSelectionLabel.setText(folderStr);
  folderSelectionLabel.setToolTipText("Folder selected.");
  toolbar.add(folderSelectionLabel);
  folderSelectionLabel.repaint();
}

void folderSelectionBtn() {
  JButton folderSelectionBtn = new JButton("Folder->open");
  folderSelectionBtn.setBounds(85, 5, 120, 24);
  folderSelectionBtn.setToolTipText("Select folder to open.");
  toolbar.add(folderSelectionBtn);
  folderSelectionBtn.addActionListener(e-> {
    folderSelectionAction();
  }
  );
}

void modeLabel() {
  JLabel modeLabel = new JLabel();
  modeLabel.setHorizontalAlignment(JLabel.CENTER);
  modeLabel.setBounds(220, 5, 45, 24);
  modeLabel.setText("Mode:");
  toolbar.add(modeLabel);
  modeLabel.repaint();
}

void modeCombo() {
  String mode[]={"Imported py5", "Module/python"};
  JComboBox comboBox = new JComboBox(mode);
  comboBox.setBounds(265, 5, 150, 24);
  comboBox.setToolTipText("Modes");
  comboBox.setSelectedIndex(0);
  // **** Change cmdStr for your system **** //
  cmdStr = "/opt/miniconda3/bin/py5-run-sketch";
  comboBox.addActionListener(e -> {
    int cboxSelection =  comboBox.getSelectedIndex();
    if (cboxSelection == 0) {
      cmdStr = "/opt/miniconda3/bin/py5-run-sketch";
    } else {
      cmdStr = "/opt/miniconda3/bin/python3";
    }
  }
  );
  toolbar.add(comboBox);
}

void runBtn() {
  runBtn = new RunButton();
  runBtn.setBounds(420, 5, 34, 34);
  runBtn.setToolTipText("Run code.");
  runBtn.addMouseListener(new MouseAdapter() {
    public void mousePressed(MouseEvent me) {
      runBtnAction();
    }
  }
  );
  toolbar.add(runBtn);
  runBtn.repaint();
}

void stopBtn() {
  stopBtn = new StopButton();
  stopBtn.setBounds(470, 5, 34, 34);
  stopBtn.setToolTipText("Stop app.");
  stopBtn.addMouseListener(new MouseAdapter() {
    public void mousePressed(MouseEvent me) {
      stopBtnAction();
    }
  }
  );
  toolbar.add(stopBtn);
  stopBtn.repaint();
}

void clearAllBtn() {
  clearAllBtn = new ClearAllButton();
  clearAllBtn.setBounds(520, 5, 34, 34);
  clearAllBtn.setToolTipText("Clear All.");
  clearAllBtn.addMouseListener(new MouseAdapter() {
    public void mousePressed(MouseEvent me) {
      clearAllAction();
    }
  }
  );
  toolbar.add(clearAllBtn);
  clearAllBtn.repaint();
}

void clearLogBtn() {
  clearLogBtn = new ClearLogButton();
  clearLogBtn.setBounds(570, 5, 34, 34);
  clearLogBtn.setToolTipText("Clear Log.");
  clearLogBtn.addMouseListener(new MouseAdapter() {
    public void mousePressed(MouseEvent me) {
      clearLogAction();
    }
  }
  );
  toolbar.add(clearLogBtn);
  clearLogBtn.repaint();
}

void editBtn() {
  editBtn = new JRadioButton("Edited");
  editBtn.setBounds(620, 0, 80, 30);
  editBtn.setToolTipText("Text has changed.");
  toolbar.add(editBtn);
  editBtn.repaint();
}

void auxWnd() {
  JCheckBox chkBox = new JCheckBox("AuxWnd");
  chkBox.setBounds(610, 25, 90, 24);
  toolbar.add(chkBox);
  chkBox.setToolTipText("Toggle Auxilliary window.");
  chkBox.addItemListener(new ItemListener() {
    public void itemStateChanged(ItemEvent evt) {
      if (evt.getStateChange()==1) {
        auxWndAction();
      } else {
        aux.setVisible(false);
      }
    }
  }
  );
  chkBox.repaint();
}

void quitBtn() {
  quitBtn = new QuitButton();
  quitBtn.setBounds(_wndW - 60, 5, 34, 34);
  quitBtn.setToolTipText("Quit editor.");
  quitBtn.addMouseListener(new MouseAdapter() {
    public void mousePressed(MouseEvent me) {
      quitBtnAction();
    }
  }
  );
  toolbar.add(quitBtn);
  quitBtn.repaint();
}

void buildWnd() {
  toolBar();
  txtArea(); // RSyntaxTextArea
  logArea();
  splitPane();
  popUpMenu();
  folderSelectionLabel();
  folderSelectionBtn();
  modeLabel();
  modeCombo();
  runBtn();
  stopBtn();
  clearAllBtn();
  clearLogBtn();
  editBtn();
  auxWnd();
  quitBtn();
  frame.setVisible(true);
}

void setup() {
  surface.setVisible(false);
  frame = new JFrame();
  frame.setBounds(100, 100, _wndW, _wndH);
  frame.setTitle("Python/Py5 Editor");
  frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
  frame.addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
      if (editBtn.isSelected()) {
        int option = JOptionPane.showConfirmDialog(frame, "Save changes?", "Exit Confirmation", JOptionPane.YES_NO_OPTION);
        if (option == JOptionPane.YES_OPTION) {
          saveAction();
        } else {
          frame.dispose(); // Dispose the frame
          exit(); // Exit the application
        }
      } else {
        exit();
      }
    }
  }
  );
  SwingUtilities.invokeLater(new Runnable() {
    public void run() {
      buildWnd();   // Build components on the EventDispatchThread(EDT).
    }
  }
  );
}

Tab1: “ClearAll”:

class ClearAllButton extends JPanel {
  int radius = 13; // Size of Btn

  ClearAllButton() {
    setOpaque(false); 
  }

  void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D)g;
    super.paintComponent(g);
    BasicStroke bs = new BasicStroke(8.0);
    Font myFont = new Font("Menlo",Font.BOLD,22);
    g2.setStroke(bs);
    g2.setColor (Color.lightGray);
    g2.drawOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
    g2.setColor (new Color(255, 255, 255));
    g2.fillOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
    g2.setColor(Color.darkGray);
    g2.setFont(myFont);
    g2.drawString("X",11,25);
  }
}
ClearAllButton clearAllBtn;

Tab2: “ClearLog”

class ClearLogButton extends JPanel {
  int radius = 13; // Size of Btn

  ClearLogButton() {
    setOpaque(false); 
  }

  void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D)g;
    super.paintComponent(g);
    BasicStroke bs = new BasicStroke(8.0);
    Font myFont = new Font("Menlo",Font.BOLD,18);
    g2.setStroke(bs);
    g2.setColor (Color.lightGray);
    g2.drawOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
    g2.setColor (new Color(255, 255, 255));
    g2.fillOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
    g2.setColor(Color.darkGray);
    g2.setFont(myFont);
    g2.drawString("x",12,23);
  }
}
ClearLogButton clearLogBtn;

Tab3: “Quit”:

class QuitButton extends JPanel {
  int radius = 13; // Size of Btn

  QuitButton() {
    setOpaque(false); 
  }

  void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D)g;
    super.paintComponent(g);
    BasicStroke bs = new BasicStroke(8.0);
    Font myFont = new Font("Menlo",Font.BOLD,22);
    g2.setStroke(bs);
    g2.setColor(Color.lightGray);
    g2.drawOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
    g2.setColor(new Color(66,66,66));
    g2.fillOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
    g2.setColor(Color.white);
    g2.setFont(myFont);
    g2.drawString("Q",11,25);
  }
}
QuitButton quitBtn;

Tab4: “Run”:

class RunButton extends JPanel {
  int radius = 13; // Size of Btn
  // Coordinates for runBtn triangle
  int x[] = {16, 16, 18};
  int y[] = {16, 18, 17};

  RunButton() {
    setOpaque(false);   
  }

  void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D)g;
    super.paintComponent(g);
    BasicStroke bs = new BasicStroke(8.0);
    g2.setStroke(bs);
    g2.setColor (Color.lightGray);
    g2.drawOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
    g2.setColor (new Color(24, 70, 183));
    g2.fillOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
    g2.setColor(Color.white);
    g2.drawPolygon(x, y, 3);
  }
}
RunButton runBtn;

Tab5: “Stop”:

class StopButton extends JPanel {
  int radius = 13; // Size of Btn

  StopButton() {
    setOpaque(false); 
  }

  void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D)g;
    super.paintComponent(g);
    BasicStroke bs = new BasicStroke(8.0);
    g2.setStroke(bs);
    g2.setColor (Color.lightGray);
    g2.drawOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
    g2.setColor (new Color(255, 255, 255));
    g2.fillOval(getWidth()/2 - radius, getHeight()/2 - radius, radius * 2, radius * 2);
    g2.setColor(Color.red);
    g.fillRect(11,11,12,12);
  }
}
StopButton stopBtn;

Py5 test code for ‘imported mode’ (py5 automatically imported):

def setup():
    size(300, 200)
    rect_mode(CENTER)

def draw():
    rect(mouse_x, mouse_y, 10, 10)

Py5 test code for ‘module mode’ (requires py5 to be imported and uses py5. prefixes):

import py5

def setup():
    py5.size(300, 200)
    py5.rect_mode(py5.CENTER)

def draw():
    py5.rect(py5.mouse_x, py5.mouse_y, 10, 10)

py5.run_sketch()

Python code example for ‘module mode’:

from tkinter import *

wnd = Tk()

C = Canvas(wnd, bg = "yellow", height = 250, width = 300)

myLine = C.create_line(108, 120, 320, 40, fill = "green")

myArc = C.create_arc(180, 150, 80, 210, start=0, extent=220, fill = "red")

oval = C.create_oval(80, 30, 140, 150, fill = "blue")

C.pack()

mainloop()

JavaFX test code for ‘imported mode’:

import javafx

def myBtnAction(event):
    print("btn was hit.")

def setup():
    size(300, 200, FX2D)
    window_title('JavaFX Button')
    canvas = get_surface().get_native()
    root = canvas.getParent()
    btn = javafx.scene.control.Button('Push Me')
    btn.setOnAction(myBtnAction)
    root.getChildren().add(btn)

Output:

App Icon Change example:

py5-run-sketch.exe for MacOS:

2 Likes

Hello @svan ,

Cool beans!
I certainly learned a lot dissecting this material. :)

I tried this and was successful and sharing some tips below.

Windows 10 or 11 Users

You can get the file paths in a command prompt with where command:

C:\Users\GLV>where python
C:\Users\GLV\AppData\Local\Programs\Python\Python313\python.exe

C:\Users\GLV>where py5-run-sketch.exe C:\Users\GLV\AppData\Local\Programs\Python\Python313\Scripts\py5-run-sketch.exe

You can’t cut and paste above into your Java code!
You MUST choose one these options:

  1. replace the \ with a /
  2. replace the \ with a \\

Reference to explain the above:
Missing semicolon error when there really is one - #4 by glv

I created strings at the top of code for my file paths to use later in the source code provided:

String sMod = "C:/Users/GLV/AppData/Local/Programs/Python/Python313/python.exe";
String sImp = "C:/Users/GLV/AppData/Local/Programs/Python/Python313/Scripts/py5-run-sketch.exe";
String sFolder = "D:/Users/py5_code";

This is also in the responses to one of your links and much simpler:

You might also try dragging the .jar files directly onto the Processing editor.
– Kevin Workman
Commented Mar 25

References:

py5 | ixora.io < James Schmitz website
https://py5coding.org/ < Welcome to py5! website

:)

Glad to see that it is doable by someone besides me. Just curious, on Windows did you use ‘miniconda3’ or any of the other environments?

I already had these installed:

  • Python
  • py5

And what I did in my last post.

I did not have JAVA_HOME in my environment variables from previous projects but that was an easy fix.

Nothing special in additional to testing the editor in the two modes with examples provided.

As an experienced Windows user I was able to navigate this and do a Windows install (filtering all the MacOS\Linux references).
At one point I just cleaned house (too many old installs) and reinstalled everything.
Beginners may be challenged with all this… or not!

Cool beans!

:)

What I’m trying to figure out is does a Windows user need miniconda3 or some other environment? It seems to be beneficial on mac and linux. From your last post it would appear that it is not necessary to get things running. If you’re not using it then my next question is do you know how to (or can you) use pip install missingModule? For example, if you try to run some python code where the requisite module is not on your system, what are your options?

As I see it, the biggest impediment to more widespread use of Python/Py5 in our environment is the installation process which, as you have seen, can be convoluted. If the kinks can be worked out so that it is a no-brainer then I think it becomes a good option for some users, particularly beginners and those programmers who value simplicity.

I can’t answer that without more context.
I do not know what this is and did not require it.

Reference (for future):

I can certainly navigate my way through a pip install if it is available or other installs.
I won’t jump without a parachute though!

I am resourceful and will find a solution.

I am not an active Python user.
This was all a fun distraction but won’t have time to explore further.

Thanks for sharing!

:)

Thanks for giving it a shot. Miniconda (or some other environment) makes it possible for you to use pip based on my observations so far on MacOS and Linux. You’re likely not that far down the road yet to understand why you might need it, but I appreciate your efforts and hope you’ll keep going.

pip.exe is available with Python out of the box:

C:\Users\GLV>where python
C:\Users\GLV\AppData\Local\Programs\Python\Python313\python.exe

C:\Users\GLV>where py5-run-sketch
C:\Users\GLV\AppData\Local\Programs\Python\Python313\Scripts\py5-run-sketch.exe

C:\Users\GLV>where pip
C:\Users\GLV\AppData\Local\Programs\Python\Python313\Scripts\pip.exe

C:\Users\GLV>pip

Usage:
  pip <command> [options]

Commands:
  install                     Install packages.
  lock                        Generate a lock file.
  download                    Download packages.
  uninstall                   Uninstall packages.
...

If the “module” is available!
This is where clear communication is important and this was not clear. My bad!

I did not have any hurdles (other than JAVA_HOME which I removed) to getting your code working once I extracted the details from your initial post… that was the biggest challenge! I am sure my posts are equally challenging for some. :)

:)

I surmised that after my post; thanks for confirming. I had ‘miniconda’ installed on my Windows box but wasn’t sure that it was being used. Based on your setup it looks like it is not required for Windows. You should be able to use pip in the future if you need it.

As you know, going through a new process is frequently complicated; there is a lot of attention to detail and trial and error which deters some folks. You just have to get through it one time and then it becomes easier once it is shown that it can be done.