This page gives an overview of the XNap Commons and describes how to integrate some of the components into existing Java applications. If you would rather like to look at source code right away, check out our Examples (or the latest from CVS) and the API Documentation.
The i18n package has been considerably improved and was moved to its own project Gettext Commons in order to decrease the footprint incurred from loading the i18n classes to a minimum.
Consequently the new i18n library has no Swing dependencies and is source compliant with Java 1.3
The new new tutorial is available here.
Often Java property files are used to store settings, unfortunately these only operate on strings and lack support for complex types or validation. The idea of the settings framework is to provide human readable serialization of complex types and to abstract from the actual storage mechanism.
Each setting is represented by an object of a type that implements org.xnap.setting.Setting and usually inherits from org.xnap.setting.AbstractSetting. The Setting interface requires methods to set, retrieve and to revert a setting to its default value. Each setting also needs to provide a unique key that identifies the setting in the back-store.
The most convenient approach to using the settings framework is to have a class that contains a publicly accessible, static field for each setting:
public class Settings { static PropertyResource backstore = new PropertyResource(); public static void load(File file) throws IOException { backstore.load(file); } public static void store(File file) throws IOException { backstore.store(file); } public final static StringSetting HOSTNAME = new StringSetting( backstore, "hostname", "localhost"); public final static IntSetting PORT = new IntSetting( backstore, "port", 4559, PortRange.MIN_PORT, PortRange.MAX_PORT); }
The backstore used in the example with the type PropertyResource is actually backed by a standard Java property file. The HOSTNAME field defines a setting that stores a hostname of some type using the key hostname and the default value localhost. The PORT field defines a setting that stores a port using the key port and the default value 4559. Moreover a validator is used to make sure the setting is always between PortRange.MIN_PORT and PortRange.MAX_PORT. In case PORT.setValue() is called with an invalid value an IllegalArgumentException is thrown.
// prints "localhost" System.out.println("Host: " + Settings.HOSTNAME.getValue()); Settings.HOSTNAME.setValue("xnap-commons.sf.net"); // prints "xnap-commons.sf.net" System.out.println("Host: " + Settings.HOSTNAME.getValue());
This example shows how to actually use the settings.
Besides support for standard data types like Integer, Boolean, String, java.awt.Color and java.awt.Font, the XNap Commons offer serialization of any object that implements java.io.Serializable and of Java 5.0 enum types:
public static enum { Paper A4, A3, LETTER }; public final static EnumSetting<Paper> PAPER = new EnumSetting<Paper>( backstore, "paper", Paper.A4);
More Information: SettingsExample.java, API Documentation
One thing we like about KDE is the flexible auto completion, so we created an implementation in Java that comes pretty close to the original. We support six different completion modes and have ready to use filesystem completion. Using it is straightforward:
JTextField textField = new JTextField(20); Completion completion = Builder.addCompletion(textField, new FileCompletionModel());
The code above will automatically add a context menu to choose the completion mode:
If you would like to make the context menu look more KDE alike, you can customize the behavior by instantiating the objects yourself instead of using Builder:
JTextField textField = new JTextField(20); Completion completion = new Completion(textField, new FileCompletionModel()); TextFieldMenu menu = new TextFieldMenu(); menu.addSeparator(); menu.add(new CompletionModeMenu(completion)); textField.addMouseListener(new PopupListener(menu));
Often times it is desirable not to remember the values that were entered in a text field like the URL bar in a web browser:
JTextField textField = new JTextField(); numberCompletion = Builder.addCompletion(textField); new CompletionSettingDirector(Settings.backstore, "url").restore(numberCompletion); ... new CompletionSettingDirector(Settings.backstore, "url").save(numberCompletion);
More Information: CompletionExample.java, API Documentation
The XNap Commons come with a variety of commonly used dialogs. Some of the dialogs make use of the Swing JOptionPane class whereas others are based on the JDialog based class DefaultDialog. The DefaultDialog class provides some padding and a right aligned button bar:
A confirm dialog that can save the user's decision right away in a BooleanSetting object. In case the setting is true the dialog does not show again the next time and returns JOptionPane.OK_OPTION:
BooleanSetting setting = new BooleanSetting(backstore, "dontShowConfirmAgain"); Dialogs.showConfirmDialog(parent, "Provided by Dialogs.showConfirmDialog().", "XNap Commmons", JOptionPane.OK_OPTION, setting);
The example will display this dialog:
When operations on files are executed it is usually polite to ask the user for confirmation. The XNap Commons provide dialogs for copy, move and delete operations that enable the user to deselect files, that should not be touched:
final File[] files = new File[] { new File("File1.txt"), new File("File2.txt"), new File("File3.txt"), new File("File4.txt"), new File("File5.txt"), new File("File6.txt"), }; File[] filesToDelete = Dialogs.showDeleteDialog(parent, files, null);
The example will display this dialog, the copy and move confirmation look similar:
Most users do not care to see exception stack traces but sometimes a trace can be useful when analyzing errors. Therefore we implemented a dialog that can display error details upon request:
try { throw new Exception("Sample Exception"); } catch (Exception e) { Dialogs.showError(parent, "Provided by Dialogs.showError()." "XNap Commons", e); }
The example will display this dialog:
The Swing JFileChooser dialog provides reasonable means to select a file but it is a little cumbersome to use when a directory needs to be chosen. In these cases DirectoryChooser can be used. The directory chooser displays a tree that contains all file system roots and a button that enables jumping to the user's home directory:
DirectoryChooser dialog = new DirectoryChooser(); dialog.setTitle("Choose Directory"); if (dialog.showChooseDialog(DialogsExample.this) == DirectoryChooser.APPROVE_OPTION) { System.out.println("Selected: " + dialog.getSelectedDirectory()); }
The example will display this dialog:
More Information: DefaultDialog API Documentation, Dialogs API Documentation
The table classes enhance the default Swing JTable with support for sorting and a context menu to toggle the visibility of columns. The sorted column is indicated by a little arrow in the column header. The classes are designed in such a way that existing code can be enhanced by just a few lines of code:
ExistingTableModel model = new ExistingTableModel(); TableSorter sorter = new TableSorter(model); JTable table = new ColoredTable(sorter); TableLayout layout = new TableLayout(table); pane.add(new JScrollPane(table));
In the example ExistingTableModel is an already existent implementation of the javax.swing.table.TableModel interface. The sorter acts as a decorator between the model and the view. It stores the order of the rows as they are currently displayed on screen. The original table model remains unaffected of any changes in the sort order.
Beware of calls that operate on row indices like getSelectedRow(). You cannot directly use these indices to fetch data from the original table model since the rows may have been resorted. The TableSorter class provides a method to convert a screen index back to the original model index (like JTable.convertColumnIndexToModel() for columns):
int row = ((TableSorter)table.getModel()).mapToIndex(table.getSelectedRow()); // use index on the original model now Object object = model.getValueAt(row, 0);
The ColoredTable class that is used as the table component is basically a JTable that draws every other row in a slightly darker color for improved reading. The TableLayout class is responsible for handling the user interaction. It handles mouse events and manages the column model.
More Information: TableExample.java, API Documentation
The concept of the closeable tabbed pane should be well known since most web browsers implement tabbed browsing by now. The CloseableTabbedPane class extends JTabbedPane and adds a little icon to the right of the tab title that removes the tab when clicked:
CloseableTabbedPane pane = new CloseableTabbedPane(); pane.addTab("label 1", new JLabel("label 1")); pane.addTab("label 2", new JLabel("label 2")); pane.addTab("chooser", new JFileChooser());
Here is a little fancier example from the XNap project that shows a chat pane using a different Look and Feel. Each chat channel is represented by a tab that has a little icon indicating the chat server type. Each channel tab can be closed whereas the first tab named "Global" is not closeable:
More Information: CloseableTabbedPaneExample.java, CloseableTabbedPane API Documentation
This is a simple convenience class for entering filenames that arranges a JTextField along with a button. When the button is pressed, a file chooser is displayed and the absolute path of the selected file is entered into the text field when the dialog is approved:
FileChooserPanel chooser = new FileChooserPanel(20); addComponent("File Chooser:", chooser);
The file can be retrieved by calling:
chooser.getFile()
The example will display this component:
More Information: FileChooserExample.java, FileChooserPanel API Documentation
The Builder class uses a factory to create components from Action objects. That by itself is nothing spectacular but when used in conjunction with the AbstractXNapAction.ICON_FILENAME property it can overcome an annoying shortcoming of Swing: Action objects by default only hold a single icon object that is used for each widget independent of component size. If the same action is used in menu items and toolbar buttons one will look ugly.
The XNap Commons Builder will however use an appropriately sized icon. The icon is searched in the path that can be configured through IconHelper.setIconPath(). If the icon is not available in the correct size it is scaled.
public class WhatsThisAction extends AbstractXNapAction { public WhatsThisAction() { putValue(Action.NAME, "What's This?"); putValue(ICON_FILENAME, "contexthelp.png"); } public void actionPerformed(ActionEvent e) { WhatsThis.enterWhatsThisMode(); } } Action action = new WhatsThisAction(); // create a JMenuItem with a 16x16 pixels icon JMenu helpMenu = new JMenu(); getJMenuBar().add(helpMenu); helpMenu.add(Builder.createMenuItem(action)); // create a ToolBarButton with a 22x22 pixels icon JToolBar toolBar = new JToolBar(); toolBar.add(Builder.createToolBarButton(action));
More Information: Builder API Documentation
The XNap Commons provide a What's This mode similar to the one found in most modern applications. Once the mode is triggered the shape of the mouse pointer changes and when a component that has been annotated by a short help message is clicked, a tooltip like window is shown displaying the message.
public class MyAction extends AbstractAction { public WhatsThisAction() { putValue(Action.NAME, "My Action"); putValue(Action.LONG_DESCRIPTION, "This action does nothing."); } public void actionPerformed(ActionEvent e) { } } JPanel panel = new JPanel(new FlowLayout()); panel.add(new JButton(new WhatsThisAction()); JList list = new JList(new String[] { "List with help" }); WhatsThis.setText(list, "Short text including HTML tags."); panel.add(list);
More Information: WhatThisExample.java, WhatsThis API Documentation
The UncaughtExceptionManager serves as the last fall back in case an unexpected runtime exception is encountered that is not handled. Usually the thread that threw the exception would silently die, but if a ThreadGroup is installed it is possible to at least notice the exception:
public class UncaughtExceptionManagerExample extends JFrame { private static UncaughtExceptionManager manager; public static void main(final String[] args) { manager = new UncaughtExceptionManager(); ErrorHandler handler = new ErrorHandler(null, manager, null, null); manager.addExceptionListener(handler); ThreadGroup tg = new ThreadGroup("AppThreadGroup") { public void uncaughtException(Thread t,Throwable e) { manager.uncaughtException(t, e); } }; // redirect any AWT exception to default exception handler which // is manager AWTExceptionHandler.install(); Thread mainRunner = new Thread(tg, "MainThread") { public void run() { setContextClassLoader(UncaughtExceptionManagerExample.class.getClassLoader()); // invoke the actual main method internalMain(args); } }; mainRunner.start(); } }
More Information: UncaughtExceptionManagerExample.java, UncaughtExceptionManager API Documentation
The static EmacsKeyBindings class allows you to load the most common Emacs keybindings for all Swing JTextComponents. A simple call to
EmacsKeyBindings.load();
enables the keybindings and also provides more advanced edit actions, e.g. kill-line, kill-ring-save, kill-region, backward-kill-word, kill-word, set-mark-command, yank, yank-pop, upcase-word, downcase-word and capitalize-word.
It also provides the Emacs kill-ring, which allows you to paste elements from the history of all cut text elements.
More Information: EmacsKeyBindings API Documentation
The class AbstractFileTransferHandler provides an abstract super class that handles file drag and drop support by taking care of the different transfer flavors that are needed on the different desktops.
Subclasses just have to override the methods AbstractFileTransferHandler.importFiles(JComponent comp, List<File> files) and AbstractFileTransferHandler.createTransferable(JComponent comp) to provide a meaningful implementation for the specific component.
Default implementations are provided for JTextFields which are used for editing absolute path names and JTrees displaying file trees:
JTextField jtf = new JTextField(); jtf.setText(new File("/home/me/file").getAbsolutePath()); DefaultTextFieldFileTransferHandler.install(jtf); jtf.setDragEnabled(true); JTree tree = new JTree(new FileTreeModel("Folders", File.listRoots())); tree.setTransferHandler(new DefaultTreeFileTransferHandler()); tree.setDragEnabled(true);
The class FileTransferable implements the Transferable interface for a list of files.
More Information Drag and Drop API Documentation