forthebadge forthebadge Gem Version

This gem was last updated on the 13.01.2024 (dd.mm.yyyy notation), at 11:53:32 o'clock.

Goals of the swing_paradise project

This project contains some add-on bindings to jruby Swing. Swing is an extension of the Abstract Window Toolkit (AWT).

It is thus only really useful for combination with jruby; and even there the project here is very incomplete. Mega-beta quality right now, at best. I do not recommend anyone to use this project as of yet, although for some limited scope, it may actually be useful.

Note that while the primary focus of this project will be on Swing-based applications, there is a secondary focus of this project as well, in that I will attempt to create as many Swing-based applications as possible, to make working on Windows suck less. With this I specifically mean being able to use custom, ad-hoc widgets that solve certain problems, such as "I want to delete the first page of this .pdf file, via a GUI" on windows. In fact, that has been one major use case why I intensified code for this project in October 2023 - I had to use windows as primary operating system every now and then, and it always annoyed me compared to Linux, so I decided to create widgets (and describe them, too) that will help me on windows. Also note that this is an ongoing effort - I can not predict how useful and extensive this will be. Stay tuned nonetheless.

Another, secondary focus, is to provide useful documentation in regards to Swing. This helps me as a mnemonic, but it may also be helpful to new users, in what they can do with the project here in the long run.

Terminology

In Java-Swing, a window is called a frame, which is represented by class JFrame.

The default layout for Java-Swing components is a border layout.

SwingParadise::BaseModule

SwingParadise::BaseModule is the core helper module of this gem.

You can include it, since it is a module, via:

require 'swing_paradise/base_module/base_module.rb'
include SwingParadise::BaseModule

See also the distributed examples if you want to know more about how to use this in practice.

Since as of 29th October 2023, this will also automatically pull in most of the important java-swing methods. I found this easier to work with, than have to remember which widgets I need.

This is done via java_import statements, such as:

java_import javax.swing.JPasswordField

So basically you can omit a few lines in your application by tapping into the BaseModule module.

Simplified quitting

Use the following method to quit easily:

do_quit

For instance, a quit button could then be done in this manner:

quit_button.on_clicked {
  do_quit
}

Useful java_import headers

include Java

java_import javax.swing.JButton
java_import javax.swing.JFrame
java_import javax.swing.JLabel
java_import javax.swing.JPanel
java_import javax.swing.JTextArea
java_import javax.swing.JScrollBar
java_import javax.swing.JTextField
java_import javax.swing.JSpinner
java_import javax.swing.SpinnerNumberModel
java_import java.lang.System
java_import java.awt.Font

Usage example for setBounds

_.setBounds(10, 20, 200, 40)  /* is: x-coordinate, y-coordinate, width, height) */
_.set_bounds(10, 20, 200, 40) /* is: x-coordinate, y-coordinate, width, height) */

Note that both variants work. I prefer .set_bounds().

JTextArea

First, let's have a look as to how a JTextArea may look like (on Windows):

The JTextArea class provides a component that displays multiple lines of text.

If only one line of input is required from the user, then a text field should be used rather than JTextArea.

In raw Java, the code for instantiating a new JTextArea goes like this:

text_area = new JTextArea(5, 20);
JScrollPane scroll_pane = new JScrollPane(text_area); 
text_area.setEditable(false);

In the code above, the API for JTextArea() follows this signature: JTextArea(int rows, int columns) - so you first pass in the number of rows, and then pass in the number of columns.

In Java, you can set the background colour to a colour of your choosing via:

import java.awt.Color;
Color color = new Color(255,0,0); /* This is red. */
text_area.setBackground(color);

The textarea is quite flexible. For instance, you can force it to ignore newlines entered by the user, thus making it essentially a single-line input entry:

textview = create_textarea
textview.getDocument.putProperty('filterNewlines', true)

As this is a bit annoying to remember, I added this method:

textview.filter_newlines

As always, you need to include SwingParadise::BaseModule for this add-on functionality.

Most of the time you probably don't want this behaviour though.

To set to a different background colour, use something like this:

textview.setBackground(Color::GREEN)

Line-wrapping and wrap-style can also be set:

textview.setLineWrap(true)
textview.setWrapStyleWord(false)

JComboBox - working with combo-boxes in Java swing and jruby

You can instantiate a new combo box via:

combo_box = JComboBox.new

Now, in order to fill it up, you can use something like this:

array = %w( apple bird cat dog eagle ferret )
array.each {|this_item|
  combo_box.addItem(this_item)
}

So .addItem() can be used.

As the above is a bit cumbersome, if you make use of SwingParadise::BaseModule, you can use the following API instead:

array = %w( apple bird cat dog eagle ferret )
combo_box(array)

To select a specific index, that is, a specific entry on that combo box, you can use the method .setSelectedIndex():

combo_box.setSelectedIndex(2) # For the third entry.

To query which index is the currently selected one on a combo-box, use .selected_index as in:

combo_box.selected_index

To obtain the selected entry, use:

combo_box.selected_item

Java::JavaAwtEvent::ActionEvent

Events in java can be found under java.awt.events.*.

class Java::JavaAwtEvent::ActionEvent is the base class for when Java Swing fires an event, such as when the user changes the content of a combo-box.

The syntax to use it, from jruby, goes like this:

widget.add_action_listener { |event|
}

So you designate a block variable; I recommend to consistently call it event, as that should simplify things.

How can you find out the specific event?

One way goes via the method .get_action_command(), such as in:

event.get_action_command

action_command = event.get_action_command

For the changed content of a combo-box, the result would comboBoxChanged.

To respond to this, you can use the following snippet:

case event.get_action_command
when /comboBoxChanged/
end

I needed this functionality for a widget that, when the user changes the combo-box content, another entry is also changed, reflecting the currently selected entry there.

Available colours

Just a simple listing of available colours in a java swing application:

Color::RED
Color::BLUE
Color::GREEN
Color::BLACK

Obtaining the height and width of a widget

Use .getBounds as in:

frame.getBounds()
height = r.height
width  = r.width

The GridLayout

A GridLayout in java-swing looks like this:

In other words: you arrange elements (widgets) in a 2D-like matrix. This is also called a grid of cells. Each component in the grid is exactly the same size.

To create a new GridLayout, in raw Java, use this:

/* API: GridLayout(int rows, int cols) */
GridLayout x = new GridLayout(3, 3); /* A 3x3 grid */

You can then add new elements to it via:

.add(new JButton("Button 1"));
.add(new JButton("Button 2"));

And so forth.

If you use SwingParadise::BaseModule then you can also make use of the .create_grid() method.

grid = create_grid('2x2')
grid.add(button('Button 1'))
grid.add(button('Button 2'))

This should be equivalent to the above mentioned Java code.

JCheckBox and checkboxes in Java swing

Let's first look at an image how checkboxes look in Java swing:

Let's next have a look at how we add checkboxes in raw Java swing:

JPanel panel = new JPanel();
panel.add(label);

JCheckBox one = new JCheckBox("one");
JCheckBox two = new JCheckBox("two");

panel.add(one);
panel.add(two);

To determine whether a JCheckBox is checked - aka selected, use this method:

checkbox.isSelected()

Or, if you use the swing_paradise gem, you can also use:

checkbox.is_selected?

To mark the checkbox as checked, use:

checkbox.setSelected(true)

JFrame

To create a new frame, aka a new JFrame, from jruby, use:

frame = JFrame.new # This would be an untitled frame.
frame = JFrame.new("Frame Title") # And this would set the title to "Frame Title".

To set a grid layout you can use:

frame.set_layout(
  java.awt.GridLayout.new(2, 2, 2, 2)
)

This is especially useful for any 2D layouts.

To exit a JFrame, as the main pane, Java Swing usually requires that you use something like this:

frame.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)

I found this a bit cumbersome to type and difficult to remember, so the SwingParadise::BaseModule allows you to use this method instead:

default_close

This one is easier for me to remember, and less to type, too.

Note that you can not add a JFrame to another JFrame. I ran into this issue in December 2023, so I had to note this down here.

The explanation I found stated this:

"JFrame is a top level container and as such it cannot be added to any other container."

Typically a JFrame contains two parts: a space area on top for the menu bar, and the content pane below the menu bar. Often the menu bar is missing, though, so the content pane is ultimately more important than the menu bar.

Further methods for the JFrame include these (as a sample):

.setVisible(boolean b) .setTitle(String title) .setSize(int width, int height) .setLocation(int horizontal, int vertical) .pack() .setDefaultCloseOperation(int operation)

By default a JFrame is not visible, so we have to use:

frame.setVisible(true)

To make it visible.

You can designate the size of the main JFrame via .setSize():

.setSize(int width, int height)
.setSize(600, 420)

Note that calling the method .pack() will resize the frame so that it tightly fits around components embedded into its content pane.

To make the whole application quit when the close-action is triggered, use:

frame.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)

JPanel

In raw jruby, you can create a new instance of JPanel by issuing this:

panel = JPanel.new

You can then assign different layouts to be used. For instance, to keep the panel left-aligned, meaning you will see new elements all appear in a linear left-to-right fashion, you can use a FlowLayout like this:

panel.layout = FlowLayout.new(FlowLayout::LEFT)

This would look similar to the following image:

This seems quite convenient and easy to use, so the swing_paradise gem tries to make this even easier. Here is the proposed API for the above two lines:

panel = create_panel { :left }

This is exactly the same as:

panel = JPanel.new
panel.layout = FlowLayout.new(FlowLayout::LEFT)

Note that FlowLayout is the default layout manager for every JPanel. The components will be laid out in a single row one after the other.

Here is another picture how this would look:

JPanel can also be used to draw onto, via the method called paintComponent. Whenever the GUI is resized, this method will be automatically called.

Take note that .paintComponent() should not be called directly; instead, use the .repaint() method in the event you wish to draw onto the panel.

The API for paintComponent in raw Java Swing goes as follows:

public void paintComponent(Graphics g)

Graphics here belongs to AWT.

Working with colours in SWING and jruby

The generic way to handle colours in java-swing goes like this:

import java.awt.Color;
Color color = new Color(255,0,0); /* This is red. */

Using no specific layout

Via:

frame.setLayout(nil)

You can avoid using any specific layout.

GridBagLayout

GridBagLayout aligns components by placing them within a grid of cells, allowing components to span more than one cell.

The following image shows how this may look like:

JButton and buttons in swing

You can add an icon to a button in swing.

First, let's show the Java version:

JButton button = new JButton(new ImageIcon("foobar.jpg"));

Next, the equivalent ruby code:

button = JButton.new(ImageIcon.new('foobar.jpg'))

If you use the swing_paradise gem, the above can be simplified a little bit further:

button = button(ImageIcon.new('foobar.jpg'))

I may extend this API to simplify this further, such as button_with_image() or something like that.

To change the background colour for a button, use the following API:

button.setBackground(Color.green) # This would make it green.

To let a JButton respond to on-click events, one has to attach a listener, via the following API:

button.addActionListener(ActionListener listener)

In jruby this is implemented via a block form, that is via:

{
}

To focus on a button, use:

button.requestFocusInWindow(); 

JLabel and text

You can right-align a JLabel widget via:

JLabel(String text, int horizontalAlignment) 
JLabel label = new JLabel("Telephone", SwingConstants.RIGHT);

In jruby-swing this would look like this:

label = JLabel.new('Telephone', SwingConstants::RIGHT)

After you created a label, you can call several methods on it.

For instance, to change the foreground colour, use:

label.setForeground(Color c)

For the background colour use:

label.setBackground(Color c)

You can also set the label to be opaque, via:

label.setOpaque(boolean b)
label.setOpaque(true)
label.setOpaque(false)

If true is passed to the latter method then the label will be transparent; if it is false, then the label will be non-transparent.

Interestingly you can also use HTML markup in a JLabel. The following example shows how this can be done:

label = JLabel.new("<html><span>This is your text</span></html>");

JColorChooser

JColorChooser can be used to pick a certain colours.

JOptionPane

JOptionPane can be used to ask the user about confirmation about certain actions, such as Do you really want to delete this file?.

JDialog

JDialog can be thought of to be a pop-up window that pops out when a message has to be displayed to the user.

BorderLayout

A BorderLayout places components in up to five areas:

top, bottom, left, right, and center.

It is the default layout manager for every java JFrame.

It looks like this:

BorderFactory

BorderFactory can be used to create a frame with a label.

Usage example in jruby:

_ = BorderFactory.createTitledBorder(' Shell ')
_.setTitleFont(Font.new('Tahoma', Font::PLAIN, 50)) # This here can be used to set the font that is used.

Font and using fonts in a java-swing application

The raw Java code goes like this:

Font use_this_font = new Font("SansSerif", Font.PLAIN, 20);

In jruby this would be:

use_this_font = Font.new('SansSerif', Font::Plain, 20)

If you want to make the button have a flat-style look, use:

button.setBorderPainted(false)
button.setFocusPainted(false)
button.setContentAreaFilled(false)

Java::JavaAwtEvent::ActionEvent and events in general

An event in a SWING application is typically of class Java::JavaAwtEvent::ActionEvent, as far as Ruby is concerned.

JTextField

JTextField is a lightweight component that allows the editing of a single line of text.

A JTextField - also known as an entry - is a subclass of the JTextComponent class.

Padding can be added to a JTextField via the method .setMargin() and passing proper insets to it.

Example:

setMargin(Insets m)

text_field = new JTextField("A text field example.");
text_field.setMargin(new Insets(10, 10, 10, 10));

JTextField can also be instantiated in this manner:

JTextField(String text, int columns)

So the second argument means how many columns this entry should have.

Documentation for JTextField can be seen here:

https://docs.oracle.com/javase/8/docs/api/javax/swing/JTextField.html

Textfields in Java-SWING via JFormattedTextField

If you have a need to hide certain input elements, such as via a user-password, you could use the following code, in Java-SWING:

import javax.swing.text.MaskFormatter;
MaskFormatter mask_formatter = new MaskFormatter("###-##-####");

JFormattedTextField has three preconfigured format types:

numbers
dates
strings

JScrollBar

First, before this subsection explains some things about a JScollBar, let us state that in many cases a developer may want to use JScrollPane instead.

JScrollBar is used to create a scrolling widget. This refers to a widget that has a vertical and a horizontal bar - both of which can be optional - allowing the mouse pointer to click and drag the child-widget, to the right, left, up or down. This is especially useful for a text-buffer widget, where regular text is displayed that overflows to the right hand side (or somewhere else).

The following image shows how this looked on Windows XP:

So, to summarize: the scroll bar is used to determine the viewing area of the component (the child-widget).

The scroll bar involves a knob that can be adjusted by the user. This is meant for use with the mouse pointer.

How to create a new instance for a scroll bar in Java?

The following code example (ScrollBarExample.java) shows this:

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

class ScrollBarExample {

  public static void main(String[] args) {
    final JFrame frame = new JFrame("ScrollBarExample");

    JScrollBar scrollBarH = new JScrollBar(JScrollBar.HORIZONTAL, 30, 20, 0, 500);
    JScrollBar scrollBarV = new JScrollBar(JScrollBar.VERTICAL,   30, 40, 0, 500);

    frame.setSize(300,200);
    frame.getContentPane().add(scrollBarH, BorderLayout.SOUTH);
    frame.getContentPane().add(scrollBarV, BorderLayout.EAST);
    frame.setVisible(true);
  }

}

Which relevant properties does the scroll bar have?

orientation: This indicates a horizontal or a vertical Scrollbar.
value:       This indicates the model's current value. The upper
             limit on the model's value is maximum - extent and the
             lower limit is minimum.
extent:      This indicates the width of the knob of the scrollbar.
minimum:     This indicates the minimum width of the track on which the scrollbar moves.
maximum:     This indicates the maximum width of the track on which the scrollbar moves.

If you instantiate a new scrollbar in jruby, using this syntax:

JScrollBar.new

then the following initial values are used:

minimum =   0
maximum = 100
value   =   0
extent  =  10

The first argument is orientation (as Integer).

Example:

JScrollBar.new(int orientation)
JScrollBar.new(1)

In other words: you can use this to determine the orientation of the scrollbar. The value orientation=0 means we will make use of a horizontal scroll bar, whereas the value orientation=1 means we will get a vertical scroll bar. Most commonly a horizontal scroll bar is used.

The most commonly used methods of the JScrollBar class are as follows:

- The addAdjustmentListener(AdjustmentListener l) method adds a
  listener to the specified JScrollBar instance. The listener
  will be notified every time a change to the model of the
  scroll bar is detected.
- The getModel() method returns the model of the scroll bar.
- The getMaximum() method returns the maximum value (maximum -
  extent) of the scrollbar.
- The getMinimum() method returns the minimum value of the scroll
  bar.
- The getOrientation() method returns the orientation of the
  scroll bar.
- The getValue() method returns the scrollbar’s value.
- The setMaximum() method is used to set the maximum value
  of the scrollbar.
- The setMinimum() method is used to set the minimum value of the scroll bar.
- The setOrientation() method is used to set the orientation of the scroll bar.
- The setValue() method is used to set the scrollbar’s value.

Checking for the escape-key being pressed

In raw Java SWING, we can use the following line of code to check whether the user has pressed the escape-key:

if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
}

In jruby SWING, this would be:

if (event.getKeyCode() == KeyEvent::VK_ESCAPE) {
}

Note that KeyEvent::VK_ESCAPE returns a number; in this case that number is 27.

The Graphics class

Available methods include:

drawLine(int xstart, int ystart, int xend, int yend)
drawRect(int xleft, int ytop, int width, int height)
drawOval(int xleft, int ytop, int width, int height)
drawString(String text, int xleft, int ybottom)
fillRect(int xleft, int ytop, int width, int height)
fillOval(int xleft, int ytop, int width, int height)
setColor(Color col)

All these int-arguments refer to pixels.

.drawLine(xstart,ystart,xend,yend) draws a line segment between the points with coordinates (xstart, ystart) and (xend, yend).

Note that .setColor(col) sets the colour of the drawing to col. This colour is used until the setColor command is used again.

The mouse and mouse-events

A mouse event in jruby is of class Java::JavaAwtEvent::MouseEvent.

In Java-Swing two interfaces process mouse events:

1) MouseListener 2) MouseMotionListener

Creating a MenuBar

The primary menu bar is called JMenuBar in Java-Swing.

To then create a menu, in raw Java-Swing, the following code could be used:

JMenuBar menu_bar = new JMenuBar(); # Create a new menubar here.
JMenu m1 = new JMenu("FILE");
menu_bar.add(m1);
JMenu m2 = new JMenu("Help");
menu_bar.add(m2);

Take note that the constructor for JMenu is this:

JMenu(String menuTitle);

So the name of the menu entry should be given, as a String.

As can be seen from the above code, we first need to create a new JMenuBar - this is the first step.

Then, now that we have a menu-bar, we can add new menus to it, of class JMenu. We use the method called .add() to add menu-entries there.

Once you are done with the menu, it is time to add it. The menu is typically added to a JFrame, via the following API:

setJMenuBar(JMenuBar menuBar)
frame.setJMenuBar(menuBar)

Take note that there are two distinct methods that are rather similar: one is called .setJMenuBar and the other one is called .setMenuBar. JMenuBar() is for Swing and MenuBar() is for AWT.

Individual Menu items behave like buttons.

If you have a need to disable an individual menu item then you can do so via:

setEnabled(boolean b)

Example in jruby-Swing for disabling a menu item:

menu_item.setEnabled(false)

To then bind an event to a menu-item, use .addActionListener.

Example for this:

first = JMenuItem.new('Item 1')
m1.add(first)
first.addActionListener {|event|
  puts 'Hello world!'
}

Choosing files via a GUI

You can use something like the following to get a file-chooser in jruby-swing:

file_chooser = JFileChooser.new
# file_chooser.setFileSelectionMode(JFileChooser::DIRECTORIES_ONLY)

result = file_chooser.showOpenDialog(nil)
case result
when JFileChooser::APPROVE_OPTION
  this_file = file_chooser.getSelectedFile.getAbsoluteFile.to_s
  # this_file = File.absolute_path(this_file)
  main_entry?.set_text(this_file)
end

Adjust accordingly to your use case.

Note that JFileChooser::APPROVE_OPTION returns 0.

Since as of January 2024 you can also use UniversalWidgets.main_file? and UniversalWidgets.set_main_file() to keep track of the chosen file.

JScrollPane

First, let's have a look how a JScrollPane may look like:

Next, let's look at a raw Java example for how to work with a JScrollPane:

import java.awt.FlowLayout;  
import javax.swing.JFrame;  
import javax.swing.JScrollPane; /* This is where it resides. */  
import javax.swing.JtextArea;  

public class JScrollPaneExample {  
  private static final long serialVersionUID = 1L;  

  private static void createAndShowGUI() {  

    // Create and set up the window.  
    final JFrame frame = new JFrame("Scroll Pane Example");  

    // Display the window.  
    frame.setSize(500, 500);  
    frame.setVisible(true);  
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  

    // set flow layout for the frame  
    frame.getContentPane().setLayout(new FlowLayout());  

    JTextArea textArea = new JTextArea(20, 20);  
    JScrollPane scrollableTextArea = new JScrollPane(textArea);  

    scrollableTextArea.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);  
    scrollableTextArea.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);  

    frame.getContentPane().add(scrollableTextArea);  
  }  
  public static void main(String[] args) {  

    javax.swing.SwingUtilities.invokeLater(new Runnable() {  
      public void run() {  
        createAndShowGUI();  
      }  
    });  
  }  
}

In jruby this may look like so:

text_area = JTextArea.new(20, 20)  
scrollable_text_area = JScrollPane.new(text_area)  
scrollable_text_area.setHorizontalScrollBarPolicy(JScrollPane::HORIZONTAL_SCROLLBAR_ALWAYS)  
scrollable_text_area.setVerticalScrollBarPolicy(JScrollPane::VERTICAL_SCROLLBAR_ALWAYS)  

Contact information and mandatory 2FA (no longer) coming up in 2022 / 2023

If your creative mind has ideas and specific suggestions to make this gem more useful in general, feel free to drop me an email at any time, via:

shevy@inbox.lt

Before that email I used an email account at Google gmail, but in 2021 I decided to slowly abandon gmail, for various reasons. In order to limit the explanation here, allow me to just briefly state that I do not feel as if I want to promote any Google service anymore when the user becomes the end product (such as via data collection by upstream services, including other proxy-services). My feeling is that this is a hugely flawed business model to begin with, and I no longer wish to support this in any way, even if only indirectly so, such as by using services of companies that try to promote this flawed model.

In regards to responding to emails: please keep in mind that responding may take some time, depending on the amount of work I may have at that moment. So it is not that emails are ignored; it is more that I have not (yet) found the time to read and reply. This means there may be a delay of days, weeks and in some instances also months. There is, unfortunately, not much I can do when I need to prioritise my time investment, but I try to consider all feedback as an opportunity to improve my projects nonetheless.

In 2022 rubygems.org decided to make 2FA mandatory for every gem owner eventually:

see https://blog.rubygems.org/2022/06/13/making-packages-more-secure.html

Mandatory 2FA will eventually be extended to all rubygems.org developers and maintainers. As I can not use 2FA, for reasons I will skip explaining here, this means that my projects will eventually be removed, as I no longer have any control over my projects hosted on rubygems.org (because I can not use 2FA).

At that point, I no longer have any control what is done to my projects since whoever is controlling the gems ecosystem took away our control here. I am not sure at which point ruby became corporate-controlled - that was not the case several years ago, so something has changed.

Ruby also only allows 2FA users to participate on the issue tracker these days:

https://bugs.ruby-lang.org/issues/18800

But this has been reverted some months ago, so it is no longer applicable. Suffice to say that I do not think that we should only be allowed to interact on the world wide web when some 'authority' authenticated us, such as via mandatory 2FA, so I hope this won't come back again.

Fighting spam is a noble goal, but when it also means you lock out real human people then this is definitely NOT a good situation to be had.