Subpages: 1. JMenus, Toolbars, and Actions overview
2. Basic text editor: part I - menus
3. Basic text editor: part II - Toolbars and Actions
4. Basic text editor: part III - Custom toolbar components
5. Basic text editor: part IV- Custom menu components
12.5 Basic text editor: part IV - custom menu components
In this section we will show how to build a custom menu component, ColorMenu, which allows selection of a color from a grid of small colored panes (which are instances of the inner class ColorMenu.ColorPane). By extending JMenu we inherit all MenuElement functionality (see 12.1.10), making custom menu creation quite easy.

Figure 12.6 Custom menu component used for quick color selection.
<<file figurer12-6.gif>>
The Code: BasicTextEditor.java
see \Chapter12\4
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
public class BasicTextEditor extends JFrame
{
// Unchanged code from section 12.4
protected JMenuBar createMenuBar()
{
// Unchanged code
JMenu mOpt = new JMenu("Options");
mOpt.setMnemonic('p');
ColorMenu cm = new ColorMenu("Foreground");
cm.setColor(m_monitor.getForeground());
cm.setMnemonic('f');
lst = new ActionListener() {
public void actionPerformed(ActionEvent e) {
ColorMenu m = (ColorMenu)e.getSource();
m_monitor.setForeground(m.getColor());
}
};
cm.addActionListener(lst);
mOpt.add(cm);
cm = new ColorMenu("Background");
cm.setColor(m_monitor.getBackground());
cm.setMnemonic('b');
lst = new ActionListener() {
public void actionPerformed(ActionEvent e) {
ColorMenu m = (ColorMenu)e.getSource();
m_monitor.setBackground(m.getColor());
}
};
cm.addActionListener(lst);
mOpt.add(cm);
menuBar.add(mOpt);
getContentPane().add(m_toolBar, BorderLayout.NORTH);
return menuBar;
}
// Unchanged code
}
class ColorMenu extends JMenu
{
protected Border m_unselectedBorder;
protected Border m_selectedBorder;
protected Border m_activeBorder;
protected Hashtable m_panes;
protected ColorPane m_selected;
public ColorMenu(String name) {
super(name);
m_unselectedBorder = new CompoundBorder(
new MatteBorder(1, 1, 1, 1, getBackground()),
new BevelBorder(BevelBorder.LOWERED,
Color.white, Color.gray));
m_selectedBorder = new CompoundBorder(
new MatteBorder(2, 2, 2, 2, Color.red),
new MatteBorder(1, 1, 1, 1, getBackground()));
m_activeBorder = new CompoundBorder(
new MatteBorder(2, 2, 2, 2, Color.blue),
new MatteBorder(1, 1, 1, 1, getBackground()));
JPanel p = new JPanel();
p.setBorder(new EmptyBorder(5, 5, 5, 5));
p.setLayout(new GridLayout(8, 8));
m_panes = new Hashtable();
int[] values = new int[] { 0, 128, 192, 255 };
for (int r=0; r<values.length; r++) {
for (int g=0; g<values.length; g++) {
for (int b=0; b<values.length; b++) {
Color c = new Color(values[r], values[g], values[b]);
ColorPane pn = new ColorPane(c);
p.add(pn);
m_panes.put(c, pn);
}
}
}
add(p);
}
public void setColor(Color c) {
Object obj = m_panes.get(c);
if (obj == null)
return;
if (m_selected != null)
m_selected.setSelected(false);
m_selected = (ColorPane)obj;
m_selected.setSelected(true);
}
public Color getColor() {
if (m_selected == null)
return null;
return m_selected.getColor();
}
public void doSelection() {
fireActionPerformed(new ActionEvent(this,
ActionEvent.ACTION_PERFORMED, getActionCommand()));
}
class ColorPane extends JPanel implements MouseListener
{
protected Color m_c;
protected boolean m_selected;
public ColorPane(Color c) {
m_c = c;
setBackground(c);
setBorder(m_unselectedBorder);
String msg = "R "+c.getRed()+", G "+c.getGreen()+
", B "+c.getBlue();
setToolTipText(msg);
addMouseListener(this);
}
public Color getColor() { return m_c; }
public Dimension getPreferredSize() {
return new Dimension(15, 15);
}
public Dimension getMaximumSize() { return getPreferredSize(); }
public Dimension getMinimumSize() { return getPreferredSize(); }
public void setSelected(boolean selected) {
m_selected = selected;
if (m_selected)
setBorder(m_selectedBorder);
else
setBorder(m_unselectedBorder);
}
public boolean isSelected() { return m_selected; }
public void mousePressed(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {
setColor(m_c);
MenuSelectionManager.defaultManager().clearSelectedPath();
doSelection();
}
public void mouseEntered(MouseEvent e) {
setBorder(m_activeBorder);
}
public void mouseExited(MouseEvent e) {
setBorder(m_selected ? m_selectedBorder :
m_unselectedBorder);
}
}
}
Understanding the Code
Class BasicTextEditor
The createMenuBar() method now creates a new JMenu titled "Options" and populates it with two ColorMenus. The first of these menus receives an ActionListener which requests the selected color, using ColorMenu's getColor() method, and assigns it as the foreground color of our editor component. Similarly, the second ColorMenu receives an ActionListener which manages our editor's background color.
Class ColorMenu
This class extends JMenu and represents a custom menu component which serves as a quick color chooser. Instance variables:
Border m_unselectedBorder: border to be used for a ColorPane (see below) when it is not selected and the mouse cursor is located outside of its bounds.
Border m_selectedBorder: border to be used for a ColorPane when it is selected and the mouse cursor is located outside of its bounds.
Border m_activeBorder: border to be used for a ColorPane when the mouse cursor is located inside its bounds.
Hashtable m_panes: a collection of ColorPanes.
ColorPane m_selected: a reference to the currently selected ColorPane.
The ColorMenu constructor takes a menu name as parameter and creates the underlying JMenu component using that name. This creates a root menu item which can be added to another menu or to a menu bar. Selecting this menu item will display its JPopupMenu component, which normally contains several simple menu items. In our case, however, we add a JPanel to it using JMenu's add(Component c) method. This JPanel serves as a container for 64 ColorPanes (see below) which are used to display the available selectable colors, as well as the current selection. A triple for cycle is used to generate the constituent ColorPanes in 3-dimensional color space. Each ColorPane takes a Color instance as constructor parameter, and each ColorPane is placed in our Hashtable collection, m_panes, using its associated Color as the key.
The setColor() method finds a ColorPane which holds a given Color. If such a component is found this method clears the previously selected ColorPane and selects a new one by calling its setSelected() method. The getColor() method simply returns the currently selected color.
The doSelection() method sends an ActionEvent to registered listeners notifying them that an action has been performed on this ColorPane, which means a new color may have been selected.
Class ColorMenu.ColorPane
This inner class is used to display a single color available for selection in a ColorMenu. It extends JPanel and implements MouseListener to process its own mouse events. This class uses the three Border variables from the parent ColorMenu class to represent its state, whether selected, unselected, or active. Instance variables:
Color m_c: color instance represented by this pane.
boolean m_selected: a flag indicating whether or not this pane is currently selected.
The ColorPane constructor takes a Color instance as parameter and stores it in our m_c instance variable. The only thing we need to do to display that color is set it as the pane's background. We also add a tool tip indicating the red, green, and blue components of this color.
All MouseListener related methods should be familiar by now. However, take note of the mouseReleased() method which plays the key role in color selection: If the mouse is released over a ColorPane we first assign the associated Color to the parenting ColorMenu component using the setColor() method (so it later can be retrieved by any attached listeners). We then hide all opened menu components by calling the MenuSelectionManager.clearSelectedPath() method since menu selection is completed at this point. Finally we invoke the doSelection() method on the parenting ColorMenu component to notify all attached listeners.
Running the Code
Experiment with changing the editor's background and foreground colors using our custom menu component available in the "Options" menu.. Note that a color selection will not affect anything until the mouse is released, and a mouse release also triggers the collapse of all menu popups in the current path. Figure 12.6 shows ColorMenu in action.
UI Guideline : Usability and Design alternatives
A more traditional approach to this exampe would be to have an elipsis on the menu option and open a Color Chooser Dialog. Consider what an improvement the presented design makes to usability. Within a limited range of colours, this design allows faster selection with the possible minor con that there is more chance of a mistake being made in the selection. However, such a mistake is low cost as it can easily be corrected. As you will see in the next chapter, knowing that you have a bounded range of input selections can be put to good use when improving a design and usability.
UI references:
Human Factors International at http://www.humanfactors.com
A Test to give you Fitt's at http://www.asktog.com


RSS feed Java FAQ News