Text Manipulation in Java – JTextComponents

OK, obviously I need some extra work on text manipulation in Java. Manipulating text in Java is a double edged sword. On one hand, it is really easy to get started. Java keeps track of the text models and deals with them automatically so you don’t really even have to have a working concept of them. On the other hand, if you want to do anything more serious than what has already been created by someone else, you have to be able to manipulate the model yourself. And, of course, in order to manipulate the model in some other way than throwing things against the wall and seeing what sticks, you actually have to understand what the model is doing.

In order to be a decent programmer, it is important to understand what the code causes the computer to do. That sounds obvious, but often we don’t really understand. Often, we don’t have any better understanding than Pavlov’s dog. [1] We simply learn that when we do something, something else happens in response. What we don’t know is what the change is doing down in the code to cause those changes to appear.

Most Java Swing components use models: [2]

Most Swing components have models. A button (JButton), for example, has a model (a ButtonModel object) that stores the button’s state — what its keyboard mnemonic is, whether it’s enabled, selected, or pressed, and so on. Some components have multiple models. A list (JList), for example, uses a ListModel to hold the list’s contents, and a ListSelectionModel to track the list’s current selection.

We don’t need to know all the details of these models. In most cases, we don’t even need to know that the Swing component uses models at all. We learn some API function to call, and Swing takes care of the rest. But that doesn’t work when we really need to customize something like a JTextPane. We not only need to know that it has a model, but how to manipulate it to store the information we want, as well as needing to know how it will effect the view.

From “How to Use Models”: [2]

Although Swing’s model architecture is sometimes referred to as a Model-View-Controller (MVC) design, it really isn’t. Swing components are generally implemented so that the view and controller are indivisible, implemented by a single UI object provided by the look and feel. The Swing model architecture is more accurately described as a separable model architecture. If you’re interested in learning more about the Swing model architecture, see A Swing Architecture Overview, an article in The Swing Connection.

Now that we have a general idea of what models do, we want to look closer at text models. Here is a list of what text models do: [3]

The JTextComponent class is the foundation for Swing text components. This class provides the following customizable features for all of its descendants:

  • A model, known as a document, that manages the component’s content.
  • A view, which displays the component on screen.
  • A controller, known as an editor kit, that reads and writes text and implements editing capabilities with actions.
  • Support for infinite undo and redo.
  • A pluggable caret and support for caret change listeners and navigation filters.

From the above description, you can see where Swing components get the Model-View-Controller (MVC) reference even if it might not be accurate with all Swing components.

A text component’s model is known as a document and is an instance of a class that implements the Document interface. … [3]

That quote is very important. The Document we are working with is the model for the text component. Whether we realize it or not, it is going to store all the information necessary to format the document the way we want. And if we truly want to manipulate the text, we must know how to manipulate the document (model).

There is only one thing left to point out. While it is true that JTextPane is a descendant of JTextComponent, JTextPane uses a special model called StyledDocument as its model. It needs this expanded model to hold the various text attributes that aren’t present in a regular text document (model).

… A document provides the following services for a text component:

  • Contains the text. A document stores the textual content in Element
    objects, which can represent any logical text structure, such as
    paragraphs, or text runs that share styles. We do not describe Element objects here.
  • Provides support for editing the text through the remove and insertString methods.
  • Notifies document listeners and undoable edit listeners of changes to the text.
  • Manages Position objects, which track a particular location within the text even as the text is modified.
  • Allows you to obtain information about the text, such as its length, and segments of the text as a string.

The Swing text package contains a subinterface of Document,
StyledDocument, that adds support for marking up the text with styles. One JTextComponent subclass, JTextPane, requires that its document be a StyledDocument rather than merely a Document.[3]

[1] Classical Conditioning – Wikipedia
[2] How to Use Models – Oracle Java Tutorial
[3] Text Component Features – Oracle Java Tutorials

JTextPane Study 2

I have done another study on the jTextPane, however it came out quite similar to the first study. Actually, there were things I liked about the first study that aren’t as elegant in the second study.

while I have added Actions (javax.swing.Action) to the program to make it easier coordinate menus with buttons, they almost seem more trouble than they are worth. For my use, when an action is performed, I need specific information that tells me where the caret was when the action was performed and adjust accordingly. Because of this, I would probably do a rewrite so my caret listener will have the information it needs.

Nevertheless, there are some things I learned while writing this, so I am going to go ahead and post it as a study.

Like before, there is an outer and inner class. The inner class is a very simple tool bar.

Here are some of the key APIs I used in the program:

import javax.swing.text.AbstractDocument;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

The StyledEditorKit contains the inner classes that are actions that function to toggle the attribute that I am working with. For instance, it contains StyledEditorKit.BoldAction(), StyledEditorKit.ItalicAction(), and various other pre-made toggles.

The StyleConstants are many of the well known constants that can be manipulated. I used StyleConstants to get information about the font in question.

SimpleAttributeSet is one of the more important functions. There are APIs that require a MutableAttributeSet. When you need a MutableAttributeSet, you can either create your own, or use a pre-made API like SimpleAttributeSet. Since I had no need to create my own, I used SimpleAttributeSet where a MutableAttributeSet was needed.

I think everything else is fairly self explanatory. Just because I’m feeling lazy, and I still need to rewrite this for another study, I’m not going to include the zip file to import the project. Instead, I’m just going to post the code in its entirety.

package GUI;

// imports
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.text.AbstractDocument;
import javax.swing.text.StyledDocument;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.JTextPane;
import javax.swing.JScrollPane;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.Action;
import javax.swing.JToggleButton;
import javax.swing.JComboBox;
import javax.swing.DefaultComboBoxModel;

public class MainJFrame extends JFrame {
    
    /*
    * These could be converted to variables later and loaded
    * when the program initially runs. At would make the
    * program customizable.
    */
    final int JTEXTPANE_X = 500;
    final int JTEXTPANE_Y = 300;
    final int SMALL_FONT = 12;
    final int MEDIUM_FONT = 16;
    final int LARGE_FONT = 20;
    
    // class variables
    AbstractDocument abstractDocument;
    JTextPane jTextPane;
    JScrollPane jTextPaneScrollPane;
    Action boldAction, italicAction;
    Action smallFontAction, mediumFontAction, largeFontAction;
    TextControlBar textControlBar;
    
    // constructor
    public MainJFrame() {
        super("JTextPane Study version 2");
        createClasswideJTextPane();
        createClasswideActions();
        textControlBar = new TextControlBar();  // instance of inner class
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.add(textControlBar, BorderLayout.NORTH);
        this.add(jTextPaneScrollPane, BorderLayout.CENTER);
    }
    
    private void createClasswideJTextPane() {
        jTextPane = new JTextPane();
        StyledDocument styledDocument = jTextPane.getStyledDocument();
        // the following error checking comes from the tutorial.
        if(styledDocument instanceof AbstractDocument) {
            abstractDocument = (AbstractDocument)styledDocument;
        } else {
            System.err.println("StyledDocument isn't an AbstractDocument");
            System.exit(-1);
        }
        jTextPane.setPreferredSize(new Dimension(JTEXTPANE_X, JTEXTPANE_Y));
        jTextPaneScrollPane = new JScrollPane(jTextPane);
        jTextPane.addCaretListener(new CaretListener() {
            @Override
            public void caretUpdate(CaretEvent event) {
                caretUpdateListener(event);
            }
        });
    }
    
    private void createClasswideActions() {
        boldAction = new StyledEditorKit.BoldAction();
        italicAction = new StyledEditorKit.ItalicAction();
        smallFontAction = new StyledEditorKit.FontSizeAction("small", SMALL_FONT);
        mediumFontAction = new StyledEditorKit.FontSizeAction("medium", MEDIUM_FONT);
        largeFontAction = new StyledEditorKit.FontSizeAction("large", LARGE_FONT);
    }
    
    public void caretUpdateListener(CaretEvent event) {
        boolean bold, italic;
        int size, currentDot, length;
        currentDot = event.getDot();
        length = abstractDocument.getLength();
        if(currentDot != length) {
            AttributeSet attributeSet;
            attributeSet = jTextPane.getCharacterAttributes();
            bold = StyleConstants.isBold(attributeSet);
            italic = StyleConstants.isItalic(attributeSet);
            size = StyleConstants.getFontSize(attributeSet);
            textControlBar.updateTextControlBar(bold, italic, size);
        }
    }
    
    static void createAndShowWindow() {
        JFrame jFrame = new MainJFrame();
        jFrame.pack();
        jFrame.setVisible(true);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowWindow();
            }
        });
    }
    
    /*
    *
    *
    * This is an inner class used as a menu bar
    *
    *
    */
    
    class TextControlBar extends JPanel {
        
        // inner class variables
        JComboBox paragraphTextSizeComboBox;
        JToggleButton boldButton;
        JToggleButton italicButton;
        JComboBox textSizeComboBox;
        DefaultComboBoxModel paragraphTextSizeComboBoxModel;
        DefaultComboBoxModel textSizeComboBoxModel;
        Object[] comboBoxEntries;
        
        // constructor
        TextControlBar() {
            boldButton = new JToggleButton(boldAction);
            italicButton = new JToggleButton(italicAction);
            comboBoxEntries = new String[] {"Small", "Medium", "Large"};
            textSizeComboBoxModel = new DefaultComboBoxModel(comboBoxEntries);
            paragraphTextSizeComboBoxModel = new DefaultComboBoxModel(comboBoxEntries);
            textSizeComboBox = new JComboBox(textSizeComboBoxModel);
            paragraphTextSizeComboBox = new JComboBox(paragraphTextSizeComboBoxModel);
            
            setLayout(new FlowLayout(FlowLayout.LEADING));
            
            this.add(paragraphTextSizeComboBox);
            paragraphTextSizeComboBox.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent event) {
                    paragraphTextSizeComboBoxActionListener();
                }
            });
            boldButton.setText("Bold");
            boldButton.setFont(new Font(Font.SERIF, Font.BOLD, 12));
            this.add(boldButton);
            italicButton.setText("Italic");
            italicButton.setFont(new Font(Font.SERIF, Font.ITALIC, 12));
            this.add(italicButton);
            this.add(textSizeComboBox);
            textSizeComboBox.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent event) {
                    textSizeComboBoxActionListener();
                }
            });
        }
        
        public void paragraphTextSizeComboBoxActionListener() {
            int index;
            SimpleAttributeSet simpleAttributeSet;
            simpleAttributeSet = new SimpleAttributeSet();
            index = paragraphTextSizeComboBox.getSelectedIndex();
            switch(index) {
                case 0:
                    StyleConstants.setFontSize(simpleAttributeSet, SMALL_FONT);
                    break;
                case 1:
                    StyleConstants.setFontSize(simpleAttributeSet, MEDIUM_FONT);
                    break;
                case 2:
                    StyleConstants.setFontSize(simpleAttributeSet, LARGE_FONT);
                    break;
                default:
                    StyleConstants.setFontSize(simpleAttributeSet, MEDIUM_FONT);
                    break;
            }
            jTextPane.setParagraphAttributes(simpleAttributeSet, true);
        }
        
        public void textSizeComboBoxActionListener() {
            int index;
            index = textSizeComboBox.getSelectedIndex();
            switch(index) {
                case 0:
                    smallFontAction.actionPerformed(null);
                    break;
                case 1:
                    mediumFontAction.actionPerformed(null);
                    break;
                case 2:
                    largeFontAction.actionPerformed(null);
                    break;
                default:
                    mediumFontAction.actionPerformed(null);
                    break;
            }
        }
        
        public void updateTextControlBar(boolean bold,
                                            boolean italic,
                                            int size) {
            boldButton.setSelected(bold);
            italicButton.setSelected(italic);
            switch(size) {
                case SMALL_FONT:
                    textSizeComboBox.setSelectedIndex(0);
                    break;
                case MEDIUM_FONT:
                    textSizeComboBox.setSelectedIndex(1);
                    break;
                case LARGE_FONT:
                    textSizeComboBox.setSelectedIndex(2);
                    break;
                default:
                    textSizeComboBox.setSelectedIndex(1);
                    break;
            }
        }
    } // end of TextControlBar class
} // End of MainJFrame class

JTextPane Study 1

If you want to do very much with Java, you will soon find yourself trying to work with formatted text. Java has several ways to deal with different types of text, but one of the most powerful is JTextPane.

Being powerful has its downside. JTextPane isn’t as easy to use as many of the other classes and methods. What it does have is the power of an entire word processor under the hood, it that’s the amount of power you feel you need.

Below are just a few of the basic methods available to JTextPane that will let you create formatted text on the computer screen. This study doesn’t cover all the methods available for JTextPane. It doesn’t even cover a fraction of the available methods. What it does is give you a toehold into the methods and function of this incredibly powerful group of classes.

You can download the project to import into NetBeans here: JTextPaneStudy.zip

Below is all the code contained in the project. It contains one file consisting of two different classes. The inner class creates a simple JPanel with a few clickable controls. The outer class creates the JTextPane and puts it all together into a Jframe.

package GUI;

// imports
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
    // These imports are for the inner class
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.FlowLayout;
import java.awt.Font;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JPanel;
import javax.swing.JComboBox;
import javax.swing.JToggleButton;

public class MainJFrame extends JFrame {
    
    // final variables
    final int WIN_WIDTH = 600;
    final int WIN_HEIGHT = 400;
    final int MEDIUM = 16;
    final int LARGE = 20;
    final int SMALL = 12;

    // class variables
    int endDot;
    JScrollPane textPaneScrollPane;
    JTextPane jTextPane;
    StyledDocument styledDocument;
    SimpleAttributeSet simpleAttributeSet;
    AttributeSet tempTextAttributeSet, endAttributeSet;
    TextControls textControls;
    
    // constructor
    public MainJFrame() {
        createJTextPaneAndListeners();
        textPaneScrollPane = new JScrollPane(jTextPane);
        textControls = new TextControls();
        this.setLayout(new BorderLayout());
        this.setTitle("JTextPane Study");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.add(textControls, BorderLayout.NORTH);
        this.add(textPaneScrollPane, BorderLayout.CENTER);
    }
    
    // creating JTextPane and add text styles to it
    private void createJTextPaneAndListeners() {
        jTextPane = new JTextPane();
        styledDocument = jTextPane.getStyledDocument();
        jTextPane.setPreferredSize(new Dimension(WIN_WIDTH, WIN_HEIGHT));
        simpleAttributeSet = new SimpleAttributeSet();
        StyleConstants.setFontFamily(simpleAttributeSet, "SansSerif");
        StyleConstants.setFontSize(simpleAttributeSet, MEDIUM);
        endAttributeSet = simpleAttributeSet.copyAttributes();
        jTextPane.setCharacterAttributes(simpleAttributeSet, true);
        jTextPane.setParagraphAttributes(simpleAttributeSet, true);
        jTextPane.addCaretListener(new CaretListener() {
            @Override
            public void caretUpdate(CaretEvent event) {
                textPaneCaretListener(event);
            }
        });
    }
    
    public void textPaneCaretListener(CaretEvent event) {
        int currentDot;
        currentDot = event.getDot();
        endDot = styledDocument.getLength();
        if(currentDot != endDot) {
            tempTextAttributeSet = jTextPane.getCharacterAttributes();
            textControls.updateButtons(tempTextAttributeSet);
        } else {
            jTextPane.setCharacterAttributes(endAttributeSet, true);
            textControls.updateButtons(endAttributeSet);
        }
    }
    
    private static void createAndShowGUI() {
        MainJFrame mainJFrame = new MainJFrame();
        mainJFrame.pack();
        mainJFrame.setVisible(true);
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
    
    /*
    *
    *
    * Inner class created for a control surface
    *
    *
    */
    
    class TextControls extends JPanel {
    
        // Variables
        private JToggleButton boldButton;
        private JToggleButton italicButton;
        private JComboBox textSizeComboBox;
    
        // constructor
        public TextControls() {
            boldButton = new JToggleButton();
            italicButton = new JToggleButton();
            textSizeComboBox = new JComboBox();

            setLayout(new FlowLayout(FlowLayout.LEADING));

            boldButton.setFont(new Font("Tahoma", 1, 11)); // NOI18N
            boldButton.setText("Bold");
            add(boldButton);
            boldButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent event) {
                    boldButtonActionPerformed(event);
                }
            });

            italicButton.setFont(new Font("Tahoma", 2, 11)); // NOI18N
            italicButton.setText("Italic");
            add(italicButton);
            italicButton.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent event) {
                    italicButtonActionPerformed(event);
                }
            });

            textSizeComboBox.setModel(new DefaultComboBoxModel(
                    new String[] { "Small", "Medium", "Large" }));
            textSizeComboBox.setSelectedIndex(1);
            add(textSizeComboBox);
            textSizeComboBox.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent event) {
                    textSizeComboBoxActionPerformed(event);
                }
            });
        } // end of constructor
        
        public void updateButtons(AttributeSet set) {
            boldButton.setSelected(StyleConstants.isBold(set));
            italicButton.setSelected(StyleConstants.isItalic(set));
            int size = StyleConstants.getFontSize(set);
            switch(size) {
                case SMALL:
                    textSizeComboBox.setSelectedIndex(0);
                    break;
                case MEDIUM:
                    textSizeComboBox.setSelectedIndex(1);
                    break;
                case LARGE:
                    textSizeComboBox.setSelectedIndex(2);
                    break;
            }
        }
        
        public void boldButtonActionPerformed(ActionEvent event) {
            StyleConstants.setBold(simpleAttributeSet, boldButton.isSelected());
            endAttributeSet = simpleAttributeSet.copyAttributes();
            jTextPane.setCharacterAttributes(simpleAttributeSet, true);
            jTextPane.requestFocusInWindow();
        }
        
        public void italicButtonActionPerformed(ActionEvent event) {
            StyleConstants.setItalic(simpleAttributeSet, italicButton.isSelected());
            endAttributeSet = simpleAttributeSet.copyAttributes();
            jTextPane.setCharacterAttributes(simpleAttributeSet, true);
            jTextPane.requestFocusInWindow();
        }
        
        public void textSizeComboBoxActionPerformed(ActionEvent event) {
            int selected = textSizeComboBox.getSelectedIndex();
            switch(selected) {
                case 0:
                    StyleConstants.setFontSize(simpleAttributeSet, SMALL);
                    break;
                case 1:
                    StyleConstants.setFontSize(simpleAttributeSet, MEDIUM);
                    break;
                case 2:
                    StyleConstants.setFontSize(simpleAttributeSet, LARGE);
                    break;
                default:
                    StyleConstants.setFontSize(simpleAttributeSet, MEDIUM);
                    break;
            }
            endAttributeSet = simpleAttributeSet.copyAttributes();
            jTextPane.setCharacterAttributes(simpleAttributeSet, true);
            jTextPane.requestFocusInWindow();
        }
        
    } // End Inner Class TextControls
    
} // End Outer Class MainJFrame

Java Graphics Programming Study 1: Simple Swing Graphics

I have been playing with Java Swing graphics programming. As it turns out, it is fairly easy and yet extremely powerful. Points, lines, colors, curves, and shapes are all created easily from existing classes. All I have to do is learn how to use them.

I would love to take full credit for this code, but most of it was created by someone else. I have been using the “Custom Graphics Programming – Java Programming Tutorial” for the raw material for this study: specifically, example 3 was my starting point.

I reworked the code to make it more to update it, make it more to my liking, and understand it better. I also extensively commented it for a more clear understanding.

If you would like to download the code I wrote, it is available for download here. You can either pull the java files out of the zip file or import the project into the NetBeans IDE.

The code consists of two different java files. No other resources other than those included with the Java Development Kit (JDK) are needed.

File # 1

package GUI;

// imports are individually listed for clarity
import java.awt.BorderLayout;       // for JFrame use
import java.awt.Color;              // for use in drawing
import java.awt.Dimension;          // for specifying JPanel size
import java.awt.Graphics;           // variable type passed to methods
import java.awt.event.MouseAdapter;         // for mouse tracking
import java.awt.event.MouseEvent;           // for mouse tracking
import java.awt.event.MouseMotionAdapter;   // for mouse tracking
import java.util.ArrayList;         // multiuse storage class
import javax.swing.JFrame;          // for creating JFrames
import javax.swing.JPanel;          // for creating JPanels
import javax.swing.SwingUtilities;  // to access the Event Dispatch Thread

public class GraphicsStudy extends JFrame {
    // class variables
    ArrayList<MultipointLine> linesArray;   // stores completed lines
    JPanel graphicsPanel;                   // used for the drawing surface
    MultipointLine currentLine;             // an individual line in progress
    
    // constructor sets up the entire JFrame window
    public GraphicsStudy() {
        // initialize the new array that will store all the finished lines
        linesArray = new ArrayList();
        // GraphicsPanel is an inside class
        // it is a JPanel that will be used for drawing
        graphicsPanel = new GraphicsPanel(600, 400);
        
        // add a mouse click listener
        // every time the mouse is clicked, start a new line
        graphicsPanel.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent event) {
                currentLine = new MultipointLine(event.getX(), event.getY());
                linesArray.add(currentLine);
            }
        });
        
        // add a mouse motion listener
        // when the mouse is dragged (button down while moving),
        // add points to the line that was created when the button
        // was initially pressed
        // then call for a repaint of the graphics area
        graphicsPanel.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent event) {
                currentLine.addPoint(event.getX(), event.getY());
                repaint();
            }
        });
        
        // create the window, give it a title,
        // add the JPanel to the JFrame to use for drawing,
        // pack the JFrame,
        // use platform specific placement of the window,
        // and finally, make the window visible
        this.setTitle("Graphics Study");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.getContentPane().add(graphicsPanel, BorderLayout.CENTER);
        this.pack();                            // pack or set size
        this.setLocationByPlatform(true);
        this.setVisible(true);
    }
    
    
    public static void main(String[] args) {
        // all the main class has to do is call the
        // constructor for the JPanel and put it on
        // the event dispatch thread
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new GraphicsStudy();
            }
        });
    }
    
    // this is an inner class. It creates the Jpanel that
    // will be used as a drawing surface.
    class GraphicsPanel extends JPanel {
        
        // this constructor isn't really needed, but I am using
        // it to set the dimensions for the JPanel
        public GraphicsPanel(int rows, int cols) {
            this.setPreferredSize(new Dimension(rows, cols));
        }
        
        // Override the paintComponent method and put the
        // graphics creating code inside it.
        // Since it is an inner class, it can access the stored
        // lines from its parent class (GraphicsStudy).
        @Override
        protected void paintComponent(Graphics graphics) {
            // call the super method to paint the background
            super.paintComponent(graphics);
            
            // set the drawing line color
            // what can I say, Magenta is my color
            graphics.setColor(Color.MAGENTA);
            
            // draw the saved lines
            // lines were stored in the GraphicsStudy class
            // but can be accessed by this inner class
            for(MultipointLine line : linesArray) {
                line.drawMultipointLine(graphics);
            }
        }
    }
    
}

File # 2:

package GUI;

// import
import java.awt.Graphics;       // for drawLine function
import java.util.ArrayList;     // to store the points in the line

public class MultipointLine {
    private ArrayList<Integer> xArray;
    private ArrayList<Integer> yArray;
    
    // constructor creates a new line and sets the first point.
    // The constructor is called when the mouse button is pressed.
    public MultipointLine(int x, int y) {
        xArray = new ArrayList();
        yArray = new ArrayList();
        addPoint(x, y);
    }
    
    // add a point to the end of the x and y arrays.
    // these points will be used to draw a bunch of tiny lines
    final public void addPoint(int x, int y) {
        xArray.add(x);
        yArray.add(y);
    }
    
    // draw out the crazy line from the storage arrays
    public void drawMultipointLine(Graphics graphics) {
        for(int x = 0; x < xArray.size() - 1; ++x) {
            graphics.drawLine(xArray.get(x), yArray.get(x),
                    xArray.get(x+1), yArray.get(x+1));
        }
    }
}

Custom Graphics Programming – Java Programming Tutorial

I have been planning to write up something about Java’s graphics abilities and how to use them. In the process of exploring Java graphics, I stumbled on this site. It is so detailed that I have to share it.

https://www3.ntu.edu.sg/home/ehchua/programming/java/J4b_CustomGraphics.html