Archives For March 2014

This morning I woke up still thinking in Java and wondering why I have to live with the positively ancient and hideous Metal Look-and-Feel (LaF). Turns out I don’t have to. As you can see above, I’ve managed to turn on the Windows 8.1 LaF on my simple test framework, with just a few extra lines of code. And I’ve re-discovered it’s portable across all the environments I want to run this on (at least, the ones I care about), primarily Windows 7 and 8 and various Linux distributions and desktops. But first, the code. This is a modification to the source file TabbedTables.java, first seen in the prior post.

    private static void createGUI() {
        try {
            // Set system Java L&F (Windows for Windows, Gnome for Linux...)
            //
            UIManager.setLookAndFeel(
                    UIManager.getSystemLookAndFeelClassName());
        }
        catch (UnsupportedLookAndFeelException |
                ClassNotFoundException |
                InstantiationException |
                IllegalAccessException exception) {
            // silently handle exception
        }

        JTabbedPane tabbedPane = new JTabbedPane();
        ImageIcon icon = createImageIcon("images/image.gif");

        tabbedPane.addTab("Tab 1", icon, makeTable(), "Sample 1");
        tabbedPane.setTabComponentAt(0, new CloseTabControl(tabbedPane));
        tabbedPane.setMnemonicAt(0, KeyEvent.VK_1);

        tabbedPane.addTab("Tab 2", icon, makeTable(), "Sample 2");
        tabbedPane.setTabComponentAt(1, new CloseTabControl(tabbedPane));
        tabbedPane.setMnemonicAt(1, KeyEvent.VK_2);

        JFrame frame = new JFrame("Test tabs and tables");

        frame.setContentPane(tabbedPane);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 600);
        frame.setVisible(true);
    }

Lines 2 through 13 are the new lines added. If you’re lazy and don’t care about defensive programming practices then you can toss the try/catch block and just copy lines 5 and 6 and be done with it. But I prefer to write a little more robustly if possible, so that try/catch block is a very small price to pay. I believe in quality code, especially in the final shipping product. In the process of adding this code block, I also removed a line of code; JFrame.setDefaultLookAndFeelDecorated(false);  . This line is no longer needed.

This code block also illustrates an interesting feature of NetBeans 8. The catch block (lines 8 through 13) is a multiple exception catch block that was introduced in Java 7. In earlier versions of Java you needed to write separate catch blocks for every exception, an onerous task, even if you had an IDE that would automatically assist you in creating them all. Once written you had all those individual catch blocks to maintain into the future. It’s no wonder that a lot of Java programmers eschewed writing try/catch unless it was absolutely necessary (as in, javac refused to compile the code). While this doesn’t remove all the pain, multiple exception catch makes it a lot easier to write, and in particular, easier to read in the future.

How does NetBeans 8 fit into this? I was given a recommendation by the NetBeans 8 IDE, and when I selected that from its dialog, it automatically reformatted the code for me. This is one key reason why a good IDE like NetBeans or IntelliJ IDE are at times vital to fast and accurate Java programming. Those tools support you in writing up-to-date compliant code, and teach you a few things along the way.

Here’s a few screen captures of the exact same code running on Linux. Both were running in VMware. The top screen capture is CentOS 6.5 (equivalent to RHEL 6.5) running Gnome 2, and the bottom is Linux Mint 15 running its Cinnamon desktop. Of note are the tabs. They don’t look quite right, and that’s because of the “hand-drawn” close button icon. I need to dig a little deeper and learn how to use a given LaF icon, using the “hand drawn” version when I can’t find it. That will make the tabs look a lot better. But for the time being, this is Good Enough.

NOTE: All my Linux installations (hardware and virtual) are now running Java 8. Even my little Raspberry Pi.

While waiting out the rain and tornado watches here in Orlando, I spent my time indoors hacking together a test framework to learn, yet again, how to drop tables (JTable) into tabbed panes (JTabbedPane) and provide a simple way to close individual tabs when needed. I decided to do all this Java hackery with NetBeans 8. NB8 was released at the same time as Java 8, and is meant to support the latest Java 8 features, including lambda expressions. Because this is research for a bigger project at work, and we’re still on Java 7 update 51, I had to forgo using all the latest features, especially lambda expressions.

I needed a framework to investigate (and frankly, re-learn) how to build tables, render data in them, and dismiss them. It’s been years since I had to do anything non-trivial with the Java Foundation Classes, and that had me scrambling through a few tutorials to come back up to speed. I knew what I wanted, just not quite how to achieve it.

For this first step, I wanted to:

  1. Create tabs with individual close buttons on each tab. You should be able to dismiss the tabs in any arbitrary order.
  2. Render independent tables on each tab.
  3. Alternately render row color, using the colors wheat2 and white (the default) in this case.

I pretty much achieved all those goals by the end of the day (in between doing all the other little Saturday chores that needed doing). What follows is the code I hacked together for this example. I will not claim this is the best example of Java programming, far from it. If you’re searching for the best idiomatic Java, then you should probably look elsewhere. My experiences with Java goes back to the mid-1990s and Java 1, with all that that implies.

As for NetBeans 8, for the most part it didn’t get in my way as I was hacking this prototype code together. It was fast and reasonably fluid, although I could have done with a bit less autocomplete. Right now I have three Java IDEs on my Windows notebook; Netbeans 8, IntelliJ IDE 13.1.1, and Eclipse. I’ve got Eclipse because of a lot of “legacy” Android projects I started there. I had every intention of moving out of Eclipse and into IntelliJ/Android Studio, but Android Studio is still under heavy development and I have real needs now, both in Android and regular Java. I’m not too happy with NetBeans 8 and its dropping Scala support, which it had in version 7.4 and earlier. IntelliJ has that support, for the latest Scala version, and I’m happy with that. IntelliJ 13.1.1 also support Java 8. If I had my way I’d migrate all my work to IntelliJ and step up from the Community edition to a fully paid-for IntelliJ license. Maybe in the near future.

I’m posting all this as much for my future benefit as for anyone else who’s interested. I don’t want to have to go scrambling to re-discover this again.

package tabbedtables;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellRenderer;

/**
 *
 * @author
 */
public class TabbedTables {
    public static Object[][] sampleData =
    {
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
        { "sample", "sample", "sample", "sample" },
    };

    private static JPanel makeTable() {
        JTable table = new JTable(new TableModel(sampleData)) {
            @Override
            public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
                Component component = super.prepareRenderer(renderer, row, col);
                //
                // Alternate row color
                //
                if (!isRowSelected(row))
                    component.setBackground(row % 2 == 0 ? getBackground() : Color.decode("0xEED8AE"));
                return component;
            }
        };
        JPanel panel = new JPanel(new GridLayout(1,0));
        table.setPreferredScrollableViewportSize(new Dimension(800, 600));
        table.setFillsViewportHeight(true);
        panel.add(new JScrollPane(table));
        return panel;
    }

    private static ImageIcon createImageIcon(String path) {
        java.net.URL imageURL = TabbedTables.class.getResource(path);
        if (imageURL != null) return new ImageIcon(imageURL);
        return null;
    }

    private static void createGUI() {
        JTabbedPane tabbedPane = new JTabbedPane();
        ImageIcon icon = createImageIcon("images/image.gif");

        tabbedPane.addTab("Tab 1", icon, makeTable(), "Sample 1");
        tabbedPane.setTabComponentAt(0, new CloseTabControl(tabbedPane));
        tabbedPane.setMnemonicAt(0, KeyEvent.VK_1);

        tabbedPane.addTab("Tab 2", icon, makeTable(), "Sample 2");
        tabbedPane.setTabComponentAt(1, new CloseTabControl(tabbedPane));
        tabbedPane.setMnemonicAt(1, KeyEvent.VK_2);

        JFrame.setDefaultLookAndFeelDecorated(false);
        JFrame frame = new JFrame("Test tabs and tables");

        frame.setContentPane(tabbedPane);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 600);
        frame.setVisible(true);
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createGUI();
            }
        });
    }
}
package tabbedtables;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.plaf.basic.BasicButtonUI;

/**
 * Creates a button to be added to the tab on a JTabbedPane allowing the end
 * user to close any arbitrary tab. This is a feature that's been available for
 * about a decade (at least) on just about any major application you care to
 * name, such as Firefox and Chrome.
 */
public class CloseTabControl extends JPanel {
    private JTabbedPane parentTabbedPane;

    /**
     * Constructed with the parent JTabbledPane reference.
     * @param pane parent JTabbedPane
     */
    public CloseTabControl(final JTabbedPane pane) {
        super(new FlowLayout(FlowLayout.LEFT,0,0));

        if (pane == null) throw new NullPointerException("JTabbedPane is null");

        parentTabbedPane = pane;
        setOpaque(false);

        JLabel label = new JLabel() {
            @Override
            public String getText() {
                int index = pane.indexOfTabComponent(CloseTabControl.this);
                if (index != -1) return pane.getTitleAt(index);
                return null;
            }
        };

        label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
        add(label);
        add(new TabButton());
        setBorder(BorderFactory.createEmptyBorder(2,0,0,0));
    }

    // This is where the key work gets done. Create a button and then
    // associate mouse listeners to indicate when the mouse moves into and
    // out of a given instance, as well as close the associated tab when
    // mouse clicked.
    //
    private class TabButton extends JButton implements ActionListener {
        public TabButton() {
            initTabButton();
        }

        // initTabButton was created to squash the "leak this in constructor"
        // warning, when all of this was in the constructor.
        //
        private void initTabButton() {
            setPreferredSize(new Dimension(20, 20));
            setToolTipText("Close this tab.");
            setUI(new BasicButtonUI());
            setContentAreaFilled(false);
            setFocusable(false);
            setBorder(BorderFactory.createEtchedBorder());
            setBorderPainted(false);
            addMouseListener(buttonMouseListener);
            setRolloverEnabled(true);
            addActionListener(this);
        }

        // This is where the tab is closed when a mouse click event is
        // recieved.
        //
        @Override
        public void actionPerformed(ActionEvent event) {
            int index = parentTabbedPane.indexOfTabComponent(CloseTabControl.this);
            if (index != -1) parentTabbedPane.remove(index);
        }

        @Override
        public void updateUI() {}

        @Override
        protected void paintComponent(Graphics graphics) {
            Graphics2D g2 = (Graphics2D) graphics.create();
            if (getModel().isPressed()) g2.translate(1,1);
            g2.setStroke(new BasicStroke(3));
            g2.setColor(Color.BLACK);
            if (getModel().isRollover()) g2.setColor(Color.RED);
            int tweek = 4;
            g2.drawLine(tweek, tweek, getWidth() - tweek - 1, getHeight() - tweek - 1);
            g2.drawLine(getWidth() - tweek - 1, tweek, tweek, getHeight() - tweek - 1);
            g2.dispose();
        }
    }

    private final static MouseListener buttonMouseListener = new MouseAdapter() {
        @Override
        public void mouseEntered(MouseEvent event) {
            if (event.getComponent() instanceof AbstractButton) {
                ((AbstractButton)event.getComponent()).setBorderPainted(true);
            }
        }

        @Override
        public void mouseExited(MouseEvent event) {
            if (event.getComponent() instanceof AbstractButton) {
                ((AbstractButton)event.getComponent()).setBorderPainted(false);
            }
        }
    };
}
package tabbedtables;

import javax.swing.table.AbstractTableModel;

/**
 *
 */
public class TableModel extends AbstractTableModel {
    private final String[] columnNames = {
        "Column 1", "Column 2", "Column 3", "Column 4"
    };

    private Object[][] data = null;

    public TableModel(Object[][] initialData) {
        data = initialData;
    }

    @Override
    public int getColumnCount() {
        return columnNames.length;
    }

    @Override
    public int getRowCount() {
        return data != null ? data.length : 0 ;
    }

    @Override
    public String getColumnName(int column) {
        return columnNames[column];
    }

    @Override
    public Object getValueAt(int row, int col) {
        return data != null ? data[row][col] : null ;
    }

    @Override
    public Class getColumnClass(int col) {
        return getValueAt(0, col).getClass();
    }

    /**
     * We want to make the tables read-only. We'll use a complex dialog to
     * do any editing. Maybe later I'll do something fancier...
     *
     * @param row
     * @param col
     * @return
     */
    @Override
    public boolean isCellEditable(int row, int col) {
        return false;
    }

    @Override
    public void setValueAt(Object value, int row, int col) {
        if (data != null) {
            data[row][col] = value;
            fireTableCellUpdated(row, col);
        }
    }
}

raspberry pi and java 8

March 27, 2014

What you’re looking at is the SwingSet2 demo, something rather old now, running via the latest Java release, Java 8 on my Raspberry Pi, on the twm desktop and on top of the latest Arch Linux for ARM. What a mouthful…

It’s been almost a month now since my last Raspberry Pi post; I was on a business trip in Kansas for three weeks. During that period of time Oracle released Java 8 on 18 March. One of the Java implementations was for ARM V6, compiled for hard float (chip-supported floating point math). As a test I also dropped the demo package on my RPi and fired up one of the demos. To be honest it was slow starting, but not horribly slow. It was slow like my computers from a good decade ago, the ones running early Pentium chips (back when Intel still called their chips Pentiums). I don’t intend to run graphical Java applications as much as service type applications.

I’m looking at Java 8 to run JavaScript via Java 8’s built-in JavaScript engine, Nashorn. The key reason is not to abandon node.js, but to shift the JavaScript portion (if possible) to run on Nashorn as apposed to Google’s V8. There are a number of reasons for doing this, one of which is security; I believe Java 8 has far more than V8. Before you bust a gasket telling me about Java’s security sins, know that the vector for Java security breaches was with older versions of Java using applets. That’s not what we’re about here. A second key reason is that I can call down into the Java libraries via JavaScript. And I can investigate manipulating the RPi’s various I/O devices via Java 8.

I don’t know when I’ll release a new image. Compressed, my 8GB images are now at 1GB, which makes it a chore to upload them, and I’m sure a chore for you to download. Although I’ve seen downloads from Sourceforge, they’ve pretty much dried up. I may make an image with Java 8 available on April 1, like I had originally intended. It’ll have Arch Linux fully up to date as well as Java 8.

For now tax season looms and other work is making my life very busy. I’ve got another two week business trip coming up, and more business travel after that. It’s a long busy stretch ahead.

Update


I installed Apache ant and built one of the sample applications, Scriptpad. In this simple example JavaScript is calling Java’s JFrame class and instantiating a running instance with a simple title. It took all of five lines of JavaScript to invoke a Java class instance. Not too shabby. This is, of course, just a trivial proof of concept. But it does illustrate the power of the JVM.

cats. yes, i said cats

March 25, 2014

what????
oh boy, a string!
yes, i'm cool

Tonight was a cat night. I needed the little guys to do their cat thing, and they may have picked up on that need. In Lucy’s case it was discovering a bag that I would have sworn was too small for her to fit into. She’s about 9 pounds soaking wet. But I thought she might like to play with it, so I just dropped it on the floor. Lucy loves to shred paper because of the sound. Instead she literally took a running dive into the bag, wiggled around so much she rolled herself over in the bag, and then popped her head out to look around. I was laughing so hard I couldn’t hold the camera steady. Lulu, the bottom photo, was too dignified to be any part of it.

I’m still working with black and white via Silver Efex Pro 2. After all this time I’m beginning to learn that harshness is not conducive to monochrome printing, but instead, softness. In particular I’m pulling the structure slider more and more to the left, with my typical structure preset on Silver Efex at -30. What was surprising was the last photo. Although I have my E-M5 set to no contrast and natural color, the light was such that I had to pull contrast out of the photo even more in post, both in Lightroom and Silver Efex Pro.

orange and white
blue wall
train wreck
missouri river bank
I recovered these from my Samsung S4 tonight, after VSCO Cam had undergone at least two updates. These were actually some of the first photos I made while in Kansas, in Leavenworth, next to the Missouri River, while there was still snow on the ground from the earlier storms. I was excited to get these, and I liked the VSCO treatment on them. When I got back to the hotel room that evening, my excitement evaporated in the face of an obstinate phone and application that refused to give me the images. In particular the images were rotated 90 degrees to the right, and the phone/software refused to allow me to rotate them back. Unlike the majority of smartphone photographers I do know how to use the phone/camera in landscape mode. And one other note, it’s in 4:3rds aspect ratio, to match what I get from my Olympus and Panasonic cameras.

When I finally gave up trying to unload my camera photos I tweeted that the experiment with the smartphone had ended. And for the time it had. I went on to concentrate using just the E-M5 and the two primes I’d brought with me, the Panasonic 25mm and the Olympus 45mm. I got what I consider a lot of good photos out of that combination.

Then today I got a email from VSCO about four more new “analogue” effects, all for free. I love free. I downloaded them, then thought maybe I should give the S4/VSCO combo another go. And then I found these photos still on the phone. I went back in and discovered I could rotate them back to normal view, and then pulled them off the S4 and on my notebook. They’re now up on my Flickr stream.

It’s hard to pick an individual favorite. But if I had to, it would probably be the third, “train wreck.” Except they were all taken together, and trying to break them up just won’t work. They were taken at the same time of the evening, on the same day, in the same evening cold. That deep, deep Kansas cold.

Now that the combination of smartphone and software are behaving again, I’ll probably give it another go. But just as I’m not one to suffer fools gladly, neither am I one to suffer screwy tools either. And I’m speaking more of the smartphone than I am of VSCO Cam. I can certainly see the creative potential of the two together, but I consider the camera on the Galaxy S4 to be almost fatally flawed. What pulls me back to trying again is VSCO Cam.