"Destroy" instance of a class

I have a class to allow the user to select a serial port using a kind-of dropdown. It lists the available ports and the user can select using the up and down arrows.

An object is instantiated when the user presses ‘P’ on the keyboard and the class shows the ‘form’. When the user is done (either canceled or accepted the selected port), the 'form should no longer be visible.

What is the correct approach? I currently set the instance to null and in the main draw() method I check for null to decide if I need to draw it or not. Or should the class contain a flag the can hide and show it? Or should I really try to “destroy” the instantiated object (I will need to read up on that, I did find some stuff regarding it).

If the user wants to show the port selection again, ‘P’ is pressed and a new instance of the class is created.

The class

///////////////////////////////
// SerialSelector class
///////////////////////////////
class SerialSelector
{
  // list of ports
  private String ports[];
  // index selected port in 'dropdown'
  private int portIdx = -1;
  // indicate that we can / should close the 'form'
  public boolean closing = false;

  /*
  SerialSelector constructor
   */
  SerialSelector()
  {
    // get list of available ports
    ports = Serial.list();
  }

  /*
  SerialSelector keypress handler
   Returns:
   true if the key was handled, else false
   */
  boolean onKeypress()
  {
    boolean keyHandled = false;
    // if it was a coded key
    if (key == CODED)
    {
      switch(keyCode)
      {
      case UP:
        if (portIdx != -1)
        {
          portIdx--;
          if (portIdx < 0 )
          {
            portIdx = ports.length - 1;
          }
        }
        keyHandled = true;
        break;
      case DOWN:
        if (portIdx != -1)
        {
          portIdx++;
          if (portIdx >= ports.length )
          {
            portIdx = 0;
          }
        }
        keyHandled = true;
        break;
      case 0x73:  // <F4>
        portIdx = -1;
        closing = true;
        keyHandled = true;
        break;
      }
    } // end-of-if (key == CODED)
    else
    {
      switch(key)
      {
      case ' ':
      case '\r':
      case '\n':
        closing = true;
        keyHandled = true;
        break;
      }
    }

    return keyHandled;
  }

  /*
  SerialSelector get selected port
   Returns:
   null if selection was canceled, else selected port name
   */
  public String getPort()
  {
    if (portIdx == -1)
    {
      return null;
    } else
    {
      return ports[portIdx];
    }
  }

  /*
  SerialSelector draw method
   */
  void draw()
  {
    String displayedPort = "No ports available";

    // if there are ports
    if (ports.length != 0)
    {
      // if no port selected, select the first one
      if (portIdx == -1)
      {
        portIdx = 0;
      }
      displayedPort = ports[portIdx];
    } //

    textSize(14);
    float txtWidth = textWidth(displayedPort);

    // form width determined by text size
    int formWidth = (int)txtWidth + 40;
    int formHeight = 40;
    int innerWidth = formWidth - 10;
    int innerHeight = formHeight - 10;
    int posX = (width - formWidth) / 2;
    int posY = (height - formHeight) / 2;

    fill(0xFF010101);
    noStroke();
    rect(posX, posY, formWidth, formHeight, 10);

    // draw inner form with border
    stroke(0xFF0060FF);
    rect(posX + 5, posY + 5, innerWidth, innerHeight, 5);

    textAlign(CENTER, CENTER);
    fill(0xFFE0E0E0);
    text(displayedPort, posX + formWidth / 2, posY + formHeight / 2 - 3);
  }
} // end-of-SerialSelector

The class has its own draw() method that is (conditionally, see above) called from the ‘normal’ draw.

/*
Main draw
 */
void draw()
{
  background(0xFF406060);

  // if the serial selector is open
  if (portSelector != null)
  {
    portSelector.draw();
  }

  visualData.draw();

  textAlign(LEFT, CENTER);
  textSize(16);
  noStroke();

  if (selectedPort == "")
  {
    fill(0xFF010101);
    text("No port selected; press 'P' to select a port", 20, 20);
  } //
  else
  {
    if (serialSynced == true)
    {
      fill(0xFF00C000);
      text(selectedPort + " synced", 20, 20);
    } //
    else
    {
      fill(0xFF70EFC0);
      text(selectedPort + " not synced", 20, 20);
    }
  }

  noLoop();
}

It also has it’s own keyPress handler (onKeypress(), called from the ‘normal’ keyPressed().

/*
Main keyPressed
 */
void keyPressed()
{
  println("main key pressed");
  // if the serial selector is open
  if (portSelector != null)
  {
    // handle the key
    portSelector.onKeypress();

    // if user accepted the choice (space, <CR> or <LF>) or canceled (<F4>)
    if (portSelector.closing == true)
    {
      String x = portSelector.getPort();
      // if it was not a cancel
      if (x != null)
      {
        // if port was open and newly selected port does not match current port
        if (mySerial != null && x.equals(selectedPort) == false)
        {
          closePort();
        }

        // if port is closed
        if (mySerial == null)
        {
          openPort(x);
        }
      } //
      else
      {
        println("canceled");
      }

      // close (destroy)
      portSelector = null;
    }
  } //
  else
  {
    // 'P' opens the serial selector
    if (key == 'P' || key == 'p')
    {
      portSelector = new SerialSelector();
    }
  }

  redraw();
}

portSelector is the instance of the SerialSelector.

I hope I’ve provided sufficient information.

Note:

  1. I know that there is a garbage collector.
  2. I come from an environment with malloc/free (and a little new/delete) :wink:

When you execute the statement -

portSelector = new SerialSelector();

Java reserves memory for the object and returns an object reference which is stored in portSelector. Provided there is an accessible reference pointing to the object it will stay in memory.

When you do portSelector = null; the object is no longer referenced so Java’s garbage collection can release the memory. Care must be taken for instance

portSelector = new SerialSelector();
ps = portSelector;
portSelector = null;

will fail to destroy the object because it is still referenced by ps. This could cause a memory leak.

Hide/show versus destroy selector
There is only one benefit to destroying the selector and that is to release memory.

There are several benefits of hiding / showing the selector

  1. do not need CPU resources to recreate the selector
  2. you can easily maintain state when hiding then showing the selector
  3. simple programming logic to determine whether the selector is visible or not
2 Likes

There’s nothing wrong with ‘rolling your own’ but it’s also fairly easy to use a JComboBox, a Swing component:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import processing.serial.*;
import processing.core.PApplet;

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

int _wndW = 600;
int _wndH = 400;

Serial myPort;  // Create object from Serial class
Object selectedItem;
int selectedIndex;
String portName;

void devices() {
  String device[] = Serial.list();
  PApplet sketch = this;
  JComboBox devices = new JComboBox(device);
  devices.setBounds(30, 30, 300, 24);
  devices.setToolTipText("devices");
  frame.add(devices);
  devices.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent evt) {
            JComboBox comboBox = (JComboBox) evt.getSource();
            selectedItem = comboBox.getSelectedItem();
            selectedIndex = comboBox.getSelectedIndex();
            println("selectedIndex = ", selectedIndex);
            println("selectedItem = ", selectedItem);
            portName = Serial.list()[selectedIndex];
            // Un-REM this line to connect
           // myPort = new Serial(sketch, portName , 9600);
        }
    });  

  frame.setVisible(true);
}

void buildWnd() {
  devices();  
}

void setup() {
  size(_wndW, _wndH); // Makes it possible to use draw();
  frame = (javax.swing.JFrame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame();
  canvas = (processing.awt.PSurfaceAWT.SmoothCanvas) ((processing.awt.PSurfaceAWT)surface).getNative();  
  frame.setBounds(900, 300, _wndW, _wndH); // Makes it possible to add swing components
  canvas.setBounds(0, 80, _wndW, _wndH - 80); // For use with draw()
  javax.swing.SwingUtilities.invokeLater(new Runnable() {
    public void run() {
      buildWnd(); // Builds components on EventDispatchThread       
    }
  }
  );
}

void draw() {
  fill(0);
  circle(300, 100, 100);
}

2 Likes

Thanks @quark and @svan.

This little project started because I see topics here where users hard code the serial port. I do not know why they do that but I decided to write an extremely simple ‘form’ to make it easy to select a port. Once completed, I will publish it here so others can maybe benefit from it.

@quark, thanks for explanation and the pros and cons.

@svan, I’m trying to learn Processing, not Java :rofl: Seriously though, I eventually might have to dig into Java a bit more because I haven’t found a way yet to suppress DTR signal in Processing to prevent reset of an Arduino at the other side of the connection. But I also haven’t been looking very hard.

Since you prefer home-made, here’s another one:

import processing.serial.*;

color BLUE = color(64, 124, 188);
color LTGRAY = color(185, 180, 180);
color YELLOW = color(245, 250, 13);
color RED = color(255, 0, 0);
color BLACK = color(0, 0, 0);
color WHITE = color(255, 255, 255);
color GREEN = color(32, 175, 47);

List list;

boolean drop = false;
String portName;
Serial myPort;
String[] devices;

void setup() {
  size(400,400);
  devices = Serial.list();
  list = new List(40, 40, 300, 24,"Select device:", WHITE, GREEN, BLACK, 16.0, devices);  
}

void draw() {
  background(BLUE);
  list.display();  
}

void mousePressed() {
  // **** List display field **** //
  if((mouseX >= list.x) && (mouseX <= list.x + list.w) && (mouseY >= list.y) && (mouseY <= list.y + list.h)){
      if (drop == true) {
        drop = false;
      } else {
        drop = true;       
      }
 }
  // **** List item selected **** //
   if((mouseX >= list.x) && (mouseX <= list.x + list.w) && (mouseY >= list.y + list.h) && (mouseY <= list.y + list.h + devices.length*list.h)){  
     String selection = list.press(mouseX, mouseY);
        println(selection);
   }
}

List class:

// Drop Down List control parts => a.)display field, b.)arrow, c.)listItems

float[] _itemY;
int selectedItem = -1;

class List {
  float x, y, w, h;
  color bkgrndColor;
  color arrwColor;
  color txtColor;
  float txtSize;
  String displayStr;
  String [] data;
    
  // Constructor
  List(float xpt, float ypt, float wt, float ht, String title, color bkgrnd, color arrw, color txt, float textSize, String[] inArray) {
    x = xpt;
    y = ypt;
    w = wt;
    h = ht;
    displayStr = title;
    bkgrndColor = bkgrnd;
    arrwColor = arrw;
    txtColor = txt;
    txtSize = textSize;    
    txtColor = txt;
    data = inArray;
  }

  String press(int mx, int my) {
    // **** list item touches **** //
    if(drop){
    if (data.length > 0) {      
      for (int j = 0; j < data.length; j++) {
        if ((mx >= list.x) && (mx <= list.x + list.w) && (my >= _itemY[j] ) && (my <= _itemY[j] + list.h)) {
          selectedItem = j;
          println("selectedItem = ",selectedItem);
          drop = false;
        }
      }
       if (selectedItem != -1) {
        return data[selectedItem];
      }
    }
    }
    return "";
  }

  void displayFieldString(String str) {
    fill(bkgrndColor);
    rect(x, y, w, h);
    fill(arrwColor);
    triangle((x+w)-17,y+5,(x+w)-3,y+5,(x+w)-10,y+18);
    fill(txtColor);
    textSize(txtSize);
    textAlign(LEFT, CENTER);
    text(str, x + 10, y - 3, w, h);
  }

  void display() {
    // **** display field **** //
    if (selectedItem == -1) {
      displayFieldString(displayStr);
    } else {
      displayFieldString(data[selectedItem]);
    }  
    // **** list **** //
    if (drop == true) {
      if (data.length > 0) {
        // **** list items **** //
        _itemY = new float[data.length];
        for (int j = 0; j < data.length; j++) {
          _itemY[j] = (y + h) + j*list.h;
          fill(bkgrndColor);
          rect(x, _itemY[j], w, list.h);
          fill(txtColor);
          textSize(txtSize);
          textAlign(LEFT, CENTER);
          text(data[j], x + 10, _itemY[j] -3, w, list.h);
        }
      }
    }
  }
}

@GoToLoop

Please do not move topics to an irrelevant category. Please see the topic title; this is not about Arduino or electronics but about the programming language used by Processing. The fact that the Serial library is involved does not change that. So I have moved it back.

I could agree if it was moved to “Processing → Coding Questions”. So if it fits better there feel, free to move it there.

First time you moved it, I did try to send you a message but you have blocked PMs.

2 Likes

@GoToLoop thanks :+1:

1 Like