Skip to main content
Engineering LibreTexts

4.3: A Graphical User Interface (GUI)

  • Page ID
    15079
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    While command-line interfaces are useful, one of the great advantages of the Java language is that its extensive class library makes it relatively easy to develop applications that employ Graphical User Interfaces (GUIs). GUIs have been around now for many years, since the production of the Macintosh in the early 1980s. Today nearly all the personal computing applications are GUI-based. Therefore, it is important that beginning programmers be able design and write programs that resemble, albeit on a simpler scale, those programs that they use every day. Among other benefits, developing the ability to write GUI programs, like the ones everyone uses today, will make it easier for you to show off your work to others, which might help motivate further interest in learning to program.

    In this and subsequent sections, we will develop an extensible GUI model that can be used with either a Java application or an applet. By extensible we mean a model that can be easily adapted and used in a wide variety of programs. GUI programming involves a computational model known as event-driven programming, which means that GUI programs react to events that are generated mostly by the user’s interactions with elements in the GUI. Therefore, we will have to learn how to use Java’s event model to handle simple events.

    Given that this is our first look at some complex topics, we will keep the discussion as simple as possible. This means we will delay discussion of certain issues, which we take up in more depth in Chapter 13.

    Java’s GUI Components

    The Java library comes with two separate but interrelated packages of GUI components, the older java.awt package and the newer javax.swing package. For the most part, the Swing classes supersede the AWT classes. For example, the java.awt.Button class is superseded by the javax.swing.JButton class, and the java.awt.TextField class is superseded by the javax.swing.JTextField class. As these examples show, the newer Swing components add an initial ’J’ to the names of their corresponding AWT counterparts.

    Figure [fig-guiscreen] illustrates how some of the main components appear in a GUI interface. As shown there, a JLabel is simply a string of text displayed on the GUI, used here as a prompt. A JTextField is an input element that can hold a single line of text. In this case, the user has input his name. A JTextArea is an output component that can display multiple lines of text. In this example, it displays a simple greeting. A JButton is a labeled control element, which is an element that allows the user to control the interaction with the program. In this example, the user will be greeted by the name input into the JTextField, whenever the JButton is clicked. As we will learn, clicking on the JButton causes an event to occur, which leads the program to take the action of displaying the greeting. Finally, all of these components are contained in a JFrame, which is a top-level container. A container is a GUI component that can contain other GUI components.

    Various GUI components from the javax.swing package. [Artwork: We need to label the components.] fig-guiscreen

    The Swing classes are generally considered to be superior to their AWT counterparts. For one thing, Swing components use a sophisticated object-oriented design known as the model-view-controller (MVC) architecture, which gives them much greater functionality than their AWT counterparts. For example, whereas an AWT Button can only have a string as its label, a Swing JButton can use an image as a label. (See Chapter 13 for a detailed discussion of the MVC architecture.)

    Second, Swing components are written entirely in Java which makes them more portable and enables them to behave the same way regardless of the operating system on which they are run. Because of their portability, Swing components are considered lightweight. By contrast, AWT classes use routines that are implemented in the underlying operating system and are therefore not easily portable. Hence, they are considered heavyweight components. Whereas a Swing JButton should look and act the same way regardless of platform, an AWT Button would have a different implementation, and hence a different look and feel, on a Macintosh and on a Windows system. In this book, we will use the new Swing classes in our programs.

    Class Inheritance: Extending a Superclass

    As you recall from Chapter 0, class inheritance is the mechanism by which a class of objects can acquire (inherit) the methods and variables of its superclasses. Just as a horse, by membership in the class of horses, inherits those attributes and behaviors of a mammal, and, more generally, those of an animal, a Java subclass inherits the variables and methods of its superclasses. We sometimes lump together an object’s attributes and behaviors and refer to them collectively as its functionality. So we say that an object of a subclass inherits the functionality of all of its superclasses.

    By the same token, just as a horse and a cow extend their mammalian attributes and behaviors in their own special ways, a Java subclass extends the functionality of its superclasses in its own special way. Thus, a subclass specializes its superclass.

    In Chapter 3, we showed how all classes in the Java hierarchy inherit the toString() method from the Object class. The lesson there was that an object in a subclass can either use or override any public method defined in any of its superclasses. In order to implement GUI programs, we need to look at another way to employ inheritance. In particular, we need to learn how to define a new class by extending an existing class.

    We noted in Chapter 2 that unless a class is explicitly defined as a subclass of some other class it is considered implicitly to be a direct subclass of Object. Thus, the GreeterApp class that we defined earlier in this chapter is a subclass of Object. We can make the relationship between GreeterApp and Object explicit by using the extends keyword when we define the GreeterApp class:

     public class GreeterApp extends Object { ... }

    Thus, the extends keyword is used to specify the subclass/superclass relationships that hold in the Java class hierarchy. We sometimes refer to the subclass/superclass relationship as the isa relationship, in the sense that a horse isa mammal, and a mammal isa animal. Thus, the extends keyword is used to define the isa relationship among the objects in the Java class hierarchy.

    Top-level Swing and AWT classes. [NOTE: REDRAW JWindow is a subclass of Window.] fig-swing1

    A top-level container is a GUI container that cannot be added to another container; it can only have components added to it. Figure [fig-swing1] is a class hierarchy that shows the relationships among some of the top-level Swing and AWT classes. For example, the javax.swing.JFrame class, which represents a top-level window, is a subclass of java.awt.Frame, and the javax.swing.JPanel is a subclass of java.awt.Panel. We can see from this figure that a JFrame isa Frame and an Frame isa Window and a Window isa Container. These subclass/superclass relationships are created in their respective class definitions by using the extends keyword as follows:

     public class JFrame extends Frame { ... }
     public class Frame extends Window { ... }
     public class Window extends Container { ... }

    As we will see in the next section, extending a class in this way enables us to create a new class by specializing an existing class.

    Top-level Windows

    Referring again to Figure [fig-swing1], notice that all of the Swing components are subclasses of the AWT Container class. This means that Swing components are Containers. They inherit the functionality of the Container class. So Swing components can contain other GUI components. That is why a JButton can contain an image.

    All GUI programs must be contained inside some kind of top-level container. Swing provides three top-level container classes: JFrame, JApplet and JDialog. For our basic GUI, we will use a JFrame as the top-level window for stand alone applications.

    A JFrame encapsulates the basic functionality of a top-level window. It has what is called a content pane, to which other Swing components, such as buttons and text fields, can be added. Also, it comes with enough built-in functionality to respond to certain basic commands, such as when the user adjusts its size or closes it.

    Figure [fig-framecapture] shows a simple top-level window as it would be displayed on the console. This window has a title ("My GUI"). It is 200 pixels wide, 150 pixels high, and its top-left corner is located at coordinates (100,150) on the console screen. Like in other graphical systems, points on the Java console always given as an ordered pair, (X, Y), with the horizontal coordinate, X, listed first, followed by the vertical coordinate, Y. The horizontal x-axis extends positively from left to right, and the vertical y-axis extends positively from top to bottom.

    The class that created and displayed this window is shown in Figure [fig-simpleframe]. Note the use of the extends

    import javax.swing.*;
    
    public class SimpleGUI extends JFrame 
    {
        public SimpleGUI(String title) 
        {   setSize(200,150);
            setLocation(100, 150);
            setTitle(title);
            setVisible(true); // Displays the JFrame
        } // SimpleGUI()
    
        public static void main(String args[]) 
        {   new SimpleGUI("My GUI");
        } // main()
    } // SimpleGUI class

    keyword to define SimpleGUI as a subclass of JFrame. As a subclass, SimpleGUI inherits all of the functionality of a JFrame (Fig. [fig-framesubclass]) . That is, it can contain other GUI components. It knows how to resize and close itself, and so on. The reason we want to define a subclass of JFrame, rather than just use a JFrame instance, is because we want eventually to give our subclass additional functionality that is specialized for our application.

    Note how SimpleGUI’s main() program creates an instance of SimpleGUI by invoking its constructor. There is no need to use a variable here because there are no further references to this object in this class. However, simply constructing a SimpleGUI will not cause it to appear on the Java console. For that to happen, it is necessary to give it a size and to call its setVisible() method. This is done in the constructor method.

    SimpleGUI is a subclass of JFrame. fig-framesubclass

    The constructor method illustrates how to use some of the methods inherited from JFrame. Figure [fig-framesubclass] shows some of the methods that SimpleGUI inherits from JFrame. We use the setSize() and setLocation() methods to set SimpleGUI’s size and location. We use the setTitle() method to set its title. And we use the setVisible() method to cause it to appear on the console.

    GUI Components for Input, Output, and Control

    To enable our top-level window to serve as a user interface, it will be necessary to give it some components. Figure [fig-swing2] provides an overview of some of the main Swing components. Generally, there are three types of components, which correspond to the three main functions of a user interface: input, output, and control. A JTextField would be an example of an input component. The user can type text into the text field, which can then be transmitted into the program. A JTextArea is an example of an output component. The program can display text in the text area. Control components enable the user to control the actions of the program. A JButton would be an example of a control component. It can be associated with an action that can be initiated whenever the user clicks it. We might also consider a JLabel to be an output component, because we can use it to prompt the user as to what type of actions to take.

    Let’s begin by creating a simple user interface, one that enables us to perform basic input, output, and control operations with a minimum of Swing components. This will allow us to demonstrate the basic principles and techniques of user-interface design and will result in a GUI that can be extended for more sophisticated applications. For this example, we will limit our application to that of simply greeting the user, just as we did in designing our command-line interface. That means that the user will be prompted to input his or her name and the program will respond by displaying a greeting (Fig. [fig-guiscreen]). We will call our GUI GreeterGUI, to suggest its interdependence with the same Greeter computational object that we used with the command-line interface.

    For this simple application, our GUI will make use of the following components:

    • A JTextField will be used to accept user input.
    • A JTextArea will serve to display the program’s output.
    • A JButton will allow the user to request the greeting.
    • A JLabel will serve as a prompt for the JTextField.

    Figure [fig-constructors] shows some of the constructors and public methods for the JTextArea, JTextField, JButton, and JLabel components. The following code segments illustrate how to use these constructors to create instances of these components:

       // Declare instance variables for the components
    private JLabel prompt;
    private JTextField inField;
    private JTextArea display;
    private JButton goButton;
       // Instantiate the components
    prompt = new JLabel("Please type your name here: ");
    inField = new JTextField(10);   // 10 chars wide
    display = new JTextArea(10, 30);// 10 rows x 30 columns
    goButton = new JButton("Click here for a greeting!");

    For this example, we use some of the simpler constructors. Thus, we create a JTextField with a size of 10. That means it can display 10 characters of input. We create a JTextArea with 10 rows of text, each 30 characters wide. We create a JButton with a simple text prompt meant to inform the user of how to use the button.

    Adding GUI Components to a Top-Level Window

    Now that we know how to create GUI components, the next task is to add them to the top-level window. A JFrame is a top-level Container (Fig. [fig-swing1]), but instead of adding the components directly to the JFrame we have to add them to the JFrame’s content pane, which is also a Container.

    Java’s Container class has several add() methods that can be used to insert components into the container:

    add(Component comp)// add comp to end of  container
    add(Component comp, int index)// add comp at index
    add(String region, Component comp) add comp at region

    The particular add() method to use depends on how we want to arrange the components in the container. The layout of a container is controlled by its default layout manager, an object associated with the container that determines the sizing and the arrangement of its contained components. For a content pane, the default layout manager is a BorderLayout. This is an arrangement whereby components may be placed in the center of the pane and along its north, south, east, and west borders (Fig. [fig-borderlayout]).

    Components are added to a border layout by using the add(String region, Component comp) method, where the String parameter specifies either "North," "South," "East," "West," or "Center." For example, to add the JTextArea to the center of the JFrame we first create a reference to its content pane and we then add the component at its center:

    Container contentPane = getContentPane(); // Get pane
    contentPane.add("Center",display); // Add JTextArea

    One limitation of the border layout is that only one component can be added to each area. This is a problem for our example because we want our prompt JLabel to be located right before the JTextField. To get around this problem, we will create another container, a JPanel, and add the prompt, the text field, and the goButton to it. That way, all of the components involved in getting the user’s input will be organized into one panel. We then add the entire panel to one of the areas on the content pane.

    JPanel inputPanel = new JPanel();
    inputPanel.add(prompt);   // Add JLabel to panel
    inputPanel.add(inField);  // Add JTextField to panel
    inputPanel.add(goButton); // Add JButton to  panel
    contentPane.add("South", inputPanel); // Add to JFrame

    The default layout for a JPanel is FlowLayout, which means that components are added left to right with the last addition going at the end of the sequence. This is an appropriate layout for this JPanel because it will place the prompt just to the left of the input JTextField.

    Controlling the GUI’s Action

    Now that we know how to place all the components on the GUI, we need to design the GUI’s controls. As mentioned earlier, GUIs use a form of event-driven programming. Anything that happens when you are using a computer—every keystroke and mouse movement—is classified as an event. As Figure [fig-eventmodel] illustrates, events are generated by the computer’s hardware and filtered up through the operating system and the application programs. Events are handled by special objects called listeners. A listener is a specialist that monitors constantly for a certain type of event. Some events, such as inserting a CD in the CD-ROM drive, are handled by listeners in the operating system. Others, such as typing input into a Web page or a Word document, are handled by listeners in a piece of application software, such as a browser or a word processor.

    Java’s event model. fig-eventmodel

    In an event-driven programming model, the program is controlled by an event loop. That is, the program repeatedly listens for events, taking some kind of action whenever an event is generated. In effect, we might portray this event loop as follows:

     Repeat forever or until the program is stopped
        Listen for events
        If event-A occurs, handle it with event-A-handler
        If event-B occurs, handle it with event-B-handler
         ...

    The event loop listens constantly for the occurrence of events and then calls the appropriate object to handle each event.

    Figure [fig-eventhier] shows some of the main types of events in the java.awt.event package. In most cases, the names of the event classes are suggestive of their roles. Thus, a MouseEvent occurs when the mouse is moved. A KeyEvent occurs when the keyboard is used. The only event that our program needs to listen for is an ActionEvent, the type of event that occurs when the user clicks the JButton.

    Java’s event hierarchy. fig-eventhier

    When the user clicks the JButton, Java will create an ActionEvent object. This object contains important information about the event, such as the time that the event occurred and the object, such as a JButton, that was the locus of the event. For our application, when the user clicks the JButton, the program should input the user’s name from the JTextField and display a greeting, such as “Hi John nice to meet you” in the JTextArea. That is, we want the program to execute the following code segment:

      String name = inField.getText();
      display.append(greeter.greet(name) + "\n");

    The first line uses the JTextField.getText() method to get the text that the user typed into the JTextField and stores it in a local variable, name. The second line passes the name to the greeter.greet() method and passes the result it gets back to the JTextArea.append() method. This will have the effect of displaying the text at the end of the JTextArea.

    In this example, we have used a couple of the standard public methods of the JTextField and JTextArea classes. For our simple GUI, the methods described in Figure [fig-constructors] will be sufficient for our needs. However, if you would like to see the other methods available for these and other Swing components, you should check Java’s online API documentation.

    The ActionListener Interface

    Given that the code segment just described will do the task of greeting the user, where should we put that code segment in our program? We want that code segment to be invoked whenever the user clicks on the goButton. You know enough Java to understand that we should put that code in a Java method. However, we need a special method in this case, one that will be called automatically by Java whenever the user clicks that button. In other words, we need a special method that the button’s listener knows how to call whenever the button is clicked.

    Java solves this problem by letting us define a pre-selected method that can be associated with the goButton. The name of the method is actionPerformed() and it is part of the ActionListener interface. In this case, an interface is a special Java class that contains only methods and constants (final variables). It cannot contain instance variables. (Be careful to distinguish this kind of interface, a particular type of Java class, form the more general kind of interface, whereby we say that a class’s public methods make up its interface to other objects.) Here’s the definition of the ActionListener interface:

    public abstract interface ActionListener 
                                    extends EventListener 
    {    public abstract void actionPerformed(ActionEvent e);
    }

    This resembles a class definition, but the keyword interface replaces the keyword class in the definition. Note also that we are declaring this interface to be abstract. An abstract interface or abstract class is one that contains one or more abstract methods. An abstract method is one that consists entirely of its signature; it lacks an implementation—that is, it does not have a method body. Note that the actionPerformed() method in ActionListener places a semicolon where its body is supposed to be.

    Declaring a method abstract means that we are leaving its implementation up to the class that implements it. This way, its implementation can be tailored to a particular context, with its signature specifying generally what the method should do. Thus, actionPerformed() should take an ActionEvent object as a parameter and perform some kind of action.

    What this means, in effect, is that any class that implements the actionPerformed() method can serve as a listener for ActionEvents. Thus, to create a listener for our JButton, all we need to do is give an implementation of the actionPerformed() method. For our program, the action we want to take when the goButton is clicked, is to greet the user by name. Thus, we want to set things up so that the following actionPerformed() method is called whenever the goButton is clicked:

     public void actionPerformed(ActionEvent e) 
    {  if (e.getSource() == goButton) 
       {   String name = inField.getText();
             display.append(greeter.greet(name) + "\n");
       }
     }

    In other words, we place the code that we want executed when the button is clicked in the body of the actionPerformed() method. Note that in the if-statement we get the source of the action from the ActionEvent object and check that it was the goButton.

    That explains what gets done when the button is clicked—namely, the code in actionPerformed() will get executed. But it doesn’t explain how Java knows that it should call this method in the first place. To set that up we must do two further things. We must place the actionPerformed() method in our GreeterGUI class, and we must tell Java that GreeterGUI will be the ActionListener for the goButton.

    The following stripped-down version of the GreeterGUI class illustrates how we put it all together:

     public class GreeterGUI extends Frame 
                                   implements ActionListener 
    { ...
      public void buildGUI() 
      {  ...
         goButton = new JButton("Click here for a greeting!");
         goButton.addActionListener(this);
         ...
      }
      ...
      public void actionPerformed(ActionEvent e) 
      {   if (e.getSource() == goButton) 
          {   String name = inField.getText();
              display.append(greeter.greet(name) + "\n");
          }
      }
      ...
    }

    First, we declare that GreeterGUI implements the ActionListener interface in the class header. This means that the class must provide a definition of the actionPerformed() method, which it does. It also means that GreeterGUI isa ActionListener. So SimpleGUI is both a JFrame and an ActionListener.

    Second, note how we use the addActionListener() method to associate the listener with the goButton:

    goButton.addActionListener(this)

    The this keyword is a self-reference—that is, it always refers to the object in which it is used. It’s like a person referring to himself by saying “I”. When used here, the this keyword refers to this GreeterGUI. In other words, we are setting things up so that the GreeterGUI will serve as the listener for action events on the goButton.

    Connecting the GUI to the Computational Object

    Figure [fig-simplegui] gives the complete source code for our GreeterGUI interface. Because there is a lot going on here, it might be helpful to go through the program carefully

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    
    public class GreeterGUI extends JFrame 
                                implements ActionListener 
    { private JTextArea display;
      private JTextField inField;
      private JButton goButton;
      private Greeter greeter;
            
      public GreeterGUI(String title) 
      { greeter = new Greeter();  
        buildGUI();
        setTitle(title);
        pack();
        setVisible(true);
      } // GreeterGUI()
      private void buildGUI() 
      { Container contentPane = getContentPane();
        contentPane.setLayout(new BorderLayout());
        display = new JTextArea(10,30);
        inField = new JTextField(10);
        goButton = new JButton("Click here for a greeting!");
        goButton.addActionListener(this);
        JPanel inputPanel = new JPanel();
        inputPanel.add(new JLabel("Input your name here: "));
        inputPanel.add(inField);
        inputPanel.add(goButton);
        contentPane.add("Center", display);
        contentPane.add("South", inputPanel);
      } // buildGUI()
      public void actionPerformed(ActionEvent e) 
      { if (e.getSource() == goButton) 
        { String name = inField.getText();
          display.append(greeter.greet(name) + "\n");
        }
      } // actionPerformed()
    }

    even though we have introduced most of its elements already. That will help us put together all of the various concepts that we have introduced.

    To begin with, note the several Java packages that must be included in this program. The javax.swing package includes definitions for all of the Swing components. The java.awt.event package includes the ActionEvent class and the ActionListener interface, and the java.awt packages contain the Container class.

    Next note how the GreeterGUI class is defined as a subclass of and as implementing the ActionListener interface. GreeterGUI thereby inherits all of the functionality of a JFrame. Plus, we are giving it additional functionality. One of its functions is to serve as an ActionListener for its goButton. The ActionListener interface consists entirely of the actionPerformed() method, which is defined in the program. This method encapsulates the actions that will be taken whenever the user clicks the goButton.

    The next elements of the program are its four instance variables, the most important of which is the Greeter variable. This is the variable that sets up the relationship between the GUI and the computational object. In this case, because the variable is declared in the GUI, we say that the GUI uses the computation object, as illustrated in Figure [fig-greeter]. This is slightly different from the relationship we set up in the command-line interface, in which the computational object uses the interface (Fig. [fig-command-line]).

    The other instance variables are for those GUI components that must be referred to throughout the class. For example, note that the goButton, inField, and display are instantiated in the buildGUI() method and referenced again in the actionPerformed() method.

    The next element in the program is its constructor. It begins by creating an instance of the Greeter computational object. It is important to do this first in case we need information from the computational object in order to build the GUI. In this case we don’t need anything from Greeter, but we will need such information in other programs.

    We’ve already discussed the fact that the constructor’s role is to coordinate the initialization of the GreeterGUI object. Thus, it invokes the buildGUI() method, which takes care of the details of laying out the GUI components. And, finally, it displays itself by calling the pack() and setVisible() methods, which are inherited from JFrame. The pack() method sizes the frame according to the sizes and layout of the components it contains. The setVisible() method is what actually causes the GUI to appear on the Java console.

    Finally, note the details of the buildGUI() method. We have discussed each of the individual statements already. Here we see the order in which they are combined. Note that we can declare the contentPane and inputPanel variables locally, because they are not used elsewhere in the class.

    There is a simple modification that we can make to GreeterGUI. The JTextField can serve both as an input element and as a control element for action events. An ActionEvent is generated whenever the user presses the Return or Enter key in a JTextField so that the JButton can be removed. Of course, it will be necessary to designate the inField as an ActionListener in order to take advantage of this feature. Make the appropriate changes to the buildGUI() and actionPerformed() methods so that the inField can function as both a control and input element. Call the new class GreeterGUI2.

    Using the GUI in a Java Application

    As you know, a Java application is a stand alone program, one that can be run on its own. We have designed our GUI so that it can easily be used with a Java application. We saw in the previous section that the GUI has a reference to the Greeter object, which is the computational object. Therefore, all we need to get the program to run as an application is a main() method.

    One way to use the GUI in an application is simply to create an instance in a main() method. The main() method can be placed in the GreeterGUI class itself or in a separate class. Here’s an example with the main in a separate class:

    public class GreeterApplication 
    {  public static void main(String args[]) 
       {  
          new GreeterGUI("Greeter");    
       }
    }

    The main() method creates an instance of GreeterGUI, passing it a string to use as its title. If you prefer, this same main() method can be incorporated directly into the GreeterGUI class.


    This page titled 4.3: A Graphical User Interface (GUI) is shared under a CC BY 4.0 license and was authored, remixed, and/or curated by Ralph Morelli & Ralph Wade via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.