JBuilder UI package concepts

General description

The com.borland.primetime.ui package contains many support classes that can be useful not only in building your user interfaces but also help you to duplicate the Look & Feel of JBuilder within your own wizards. The majority of these classes are public yet they aren't fully documented as part of the OpenTools API. All of the classes are Swing-based. These are the ones discussed here:


Detailed description of feature/subsystem

ButtonStrip and ButtonStripConstrained

ButtonStrip extends JPanel and uses a FlowLayout to display a single row of buttons either vertically or horizontally. The default layout is horizontal with right-alignment and a horizontal/vertical gap of 5 pixels, but you can change this with constructor parameters or by using either the setAlignment() or setOrientation() methods after constructing ButtonStrip:
  ButtonStrip buttonStrip = new ButtonStrip(FlowLayout.RIGHT, 5);
  buttonStrip.add(okButton);
  buttonStrip.add(cancelButton);
Unfortunately, the FlowLayout used by ButtonStrip doesn't display one or more buttons when there is insufficient space to draw them completely. For that reason there is the alternative ButtonStripConstrained class which uses GridBagLayout (although it only supports a horizontal layout with right-alignment of the components at this time). This layout makes better use of the available space and clips buttons that don't fit completely.
  ButtonStripConstrained buttonStrip = new ButtonStripConstrained();
  buttonStrip.add(okButton);
  buttonStrip.add(cancelButton);

CheckTree and CheckTreeNode

These classes (along with support classes CheckTreeCellEditor, CheckTreeCellRenderer, CheckCellRenderer, and CheckBoxCellRenderer) provide an implementation of a JTree where each CheckTreeNode includes a checkbox. An ItemListener event fires when a checkbox is checked or unchecked by the user.
  public class MyPropertyPanel extends JPanel  {
    public class OptionTreeNode extends CheckTreeNode {
      public OptionTreeNode(String label, boolean isCheckable, OptionTreeNode parent) {
        super(label, isCheckable);
        setText(label);
        if (parent != null) {
          parent.add(this);
        }
      }
    }
    JTree optionTree = new CheckTree();
    OptionTreeNode root = new OptionTreeNode("Root", false, null);
    OptionTreeNode optionsGroup = new OptionTreeNode("Options", false, root);
    OptionTreeNode showErrorsOption = new OptionTreeNode("Show error messages", true,
       optionsGroup);
    DefaultTreeModel model = new DefaultTreeModel(root);

    public MyPropertyPanel() {
      try  {
        jbInit();
      }
      catch(Exception ex) {
        ex.printStackTrace();
      }
    }

    public void writeProperties() {
      optionTree.stopEditing();
      System.out.println("Show error messages " + showErrorsOption.isChecked());
    }
  
    public void readProperties() {
      optionTree.stopEditing();
      showErrorsOption.setChecked(false);
    }
  
    void jbInit() throws Exception {
      // build UI
      optionTree.setModel(model);
    }
  }

ColorCombo and ColorPanel

ColorPanel extends JPanel. It displays sixteen colors in a grid on the left, and eight custom colors (optionally passed as parameters) in a grid to the right. The number of displayed rows defaults to 2.

ColorCombo extends JComboBox. It displays the currently selected color in its edit field and displays a ColorPanel as its drop-down box when it's clicked. The control fires ActionListener events when a value is selected. It doesn't fire ItemListener events.

 ColorCombo cc = new ColorCombo(null, 2, ColorCombo.RIGHT);
 cc.setSelectedColor(Color.red);
 cc.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
     Color color = cc.getSelectedColor();
   }
 });

CompositeIcon

CompositeIcon takes an array of icons and displays them in a single row. The row height is that of the tallest icon in the array and the icons are centered vertically in the row.

DefaultDialog and DialogValidator

DefaultDialog extends JDialog. It provides some convenient mechanisms to support your dialog design. These include auto-centering, frame finding, default button handling, and minimum size enforcement. When the user closes the dialog by choosing the OK button and if you provided an optional component using the DialogValidator interface in your showModalDialog() call, the DefaultDialog invokes that interface, permitting it to decide if the dialog can be closed.

DefaultDialog can provide a shell to display your panel:

MyPanel panel = new MyPanel();
if (DefaultDialog.showModalDialog(Browser.getActiveBrowser(), "My Title", panel, null, null)) {
  System.out.println("pressed OK");
}

Or you can extend it for more convenient access to its features:

MyDialog dlg = new MyDialog(Browser.getActiveBrowser(), "My Title", true);
dlg.show();

public class MyDialog extends DefaultDialog {
  public SelectWindowDialog(Component owner, String title, boolean modal) {
    super(owner, title, modal);
    setAutoCenter(true);
  }
}

EdgeBorder

The EdgeBorder class implements the Border interface, providing a two-pixel inset where shadow and highlight 3D effects are drawn.
statusPanel.setBorder(new EdgeBorder(EdgeBorder.EDGE_TOP));

ImageListIcon

ImageListIcon allows you to extract a single Icon from a horizontal strip. Each bitmap is square and each has the same dimensions.
public static Image IMAGE_ACTIONS = ImageLoader.loadFromResource("icons16x16.gif",
    BrowserIcons.class);
public static ImageListIcon ICON_CUT   = new ImageListIcon(IMAGE_ACTIONS, 16, 0);
public static ImageListIcon ICON_COPY  = new ImageListIcon(IMAGE_ACTIONS, 16, 1);
public static ImageListIcon ICON_PASTE = new ImageListIcon(IMAGE_ACTIONS, 16, 2);

LazyTreeNode

This class extends javax.swing.tree.TreeNode. You should use LazyTreeNode when, for performance or other reasons, you want to provide access only to your children when the user asks for them. When that happens, your implementation of the createNodes() method is called to produce them. For example,
setRoot(new LazyTreeNode() {
  public TreeNode[] createNodes() {
    if (myList == null) {
      return EMPTY_NODES;
    }

    TreeNode[] nodes = new TreeNode[myList.size()];
    for (int index = 0; index < nodes.length; index++) {
      nodes[index] = (TreeNode)myList.get(index);
    }
    return nodes;
  }
);

ListPanel

ListPanel is an abstract class and that extends JPanel. It provides a scrolling JList next to five buttons which are by default labeled "Add...", "Edit...", "Remove", "Move Up", and "Move Down." You can change the first three labels by using the appropriate constructor to construct the class. If you override the getListCellRendererComponent() method, you can do custom rendering of the list elements, such as supplying a different color for particular elements.
public class MyPanel extends ListPanel {

  public MyPanel() {
  }

  public Component getListCellRendererComponent(JList list, Object value,
      int index, boolean isSelected, boolean cellHasFocus) {
    defaultListCellRenderer.getListCellRendererComponent(list,
        getElementName(value), index, isSelected, cellHasFocus);
    if (value instanceof JPanel) {
      defaultListCellRenderer.setForeground(Color.gray);
    }
    return defaultListCellRenderer;
  }

  // Called on Add...
  protected Object promptForElement() {
    return null;
  }

  // Called on Edit...
  protected Object editElement(Object listElement) {
    return null;
  }
}

MergeTreeNode

MergeTreeNode extends javax.swing.tree.DefaultMutableTreeNode. Use MergeTreeNode if you have a JTree that you need to update while still preserving any node expansions. Usually you do this in implementations that provide structure pane content. First build a second instance of the tree and then invoke the mergeChildren() method to update the original tree.
public class MyTextStructure extends TextStructure {

  public MyTextStructure() {
    treeModel.setRoot(new MyMergeTreeNode(null));
  }

  class MyMergeTreeNode extends MergeTreeNode {
    public MyMergeTreeNode(Object userObject) {
      super(userObject);
    }

    public void sortChildren() {
      MergeTreeNode[] array = getChildrenArray();
      if (array == null)
        return;
      Arrays.sort(array, new Comparator() {
        public int compare(Object o1, Object o2) {
          // Do comparison here between two tree nodes
          return 0;
        }
      });
      children = new Vector(Arrays.asList(array));
      sortDescendants();
    }

    public void sortDescendants() {
      if (children != null) {
        Enumeration e = children.elements();
        while (e.hasMoreElements()) {
          ((MyMergeTreeNode)e.nextElement()).sortChildren();
        }
      }
    }
  }

  public void updateStructure(Document doc) {

    final MyMergeTreeNode newRoot = new MyMergeTreeNode(null);
    try {
      // Build a new structure tree using newRoot here
      // Prepare an object that updates the model
      Runnable update =
        new Runnable() {
          public void run() {
            MyMergeTreeNode root = (MyMergeTreeNode)treeModel.getRoot();

            // Merge the new model into the old so that expansion paths can be
            // preserved
            root.mergeChildren(newRoot);

            // Sort everything including the top level of the structure tree
            root.sortChildren();

            // Update the display
            treeModel.nodeStructureChanged(root);
          }
        };

      // Update the model on the main swing thread...
      if (SwingUtilities.isEventDispatchThread())
        update.run();
      else
        SwingUtilities.invokeLater(update);

    }
    catch (java.io.IOException ex) {
    }
  }
}

SearchTree

SearchTree extends JTree. When you enter a number or letter or one of the supported regular expression characters, it displays a JTextField with the text "Search for:" followed by the input. With each character entered, it attempts to match the entire string with a visible node causing the first matched node to be selected. If the selected node has children, entering a period while depressing Ctrl expands the node. The Enter or Esc key cancels the window. For this to work, each TreeNode must supply the same text in its toString() method as it is displaying in the tree.
public class MyPanel extends JPanel {
  BorderLayout layout = new BorderLayout();
  JScrollPane scroller = new JScrollPane();
  SearchTree tree = new SearchTree() {
    public void updateUI() {
      super.updateUI();
      unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_A, Event.CTRL_MASK));
    }
  };

  public MyPanel() {
    try {
      jbInit();
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private void jbInit() throws Exception {
    this.setLayout(layout);
    this.add(scroller, BorderLayout.CENTER);
    tree.setPreserveExpansion(true);
    scroller.getViewport().add(tree, null);
  }
}

Splitter

Splitter extends JPanel. It is intended to hold one or two JComponent objects. If there is more than one, a draggable bar that is used to adjust the allocation of space between components appears. The components can be oriented vertically or horizontally.
SplitterTestFrame f = new SplitterTestFrame();
f.setBounds(100, 100, 500, 500);
f.show();

public class SplitterTestFrame extends JFrame {

  Splitter splitterA = new Splitter(true, Splitter.PROPORTIONAL, 0.8f);
  Splitter splitterB = new Splitter(false, Splitter.PROPORTIONAL, 0.2f);
  Splitter splitterC = new Splitter(true, Splitter.PROPORTIONAL, 0.5f);
  JButton pv = new JButton("ProjectView");
  JButton sv = new JButton("StructureView");
  JButton cv = new JButton("ContentView");
  JButton mv = new JButton("MessageView");

  public SplitterTestFrame() {
    try  {
      jbInit();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  private void jbInit() throws Exception {
    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(splitterA, BorderLayout.CENTER);

    splitterA.setFirstComponent(splitterB);
    splitterA.setSecondComponent(mv);
    splitterA.setProportion(0.8f);

    pv.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        mv.setVisible(!mv.isVisible());
        splitterA.validate();
      }
    });

    sv.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        splitterB.setDividerSize(splitterB.getDividerSize() > 4 ? 4 : 10);
      }
    });

    cv.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        pv.setVisible(!pv.isVisible());
        splitterB.validate();
      }
    });

    mv.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        sv.setVisible(!sv.isVisible());
        splitterB.validate();
      }
    });

    splitterB.setFirstComponent(splitterC);
    splitterB.setSecondComponent(cv);
    splitterB.setProportion(0.2f);

    splitterC.setFirstComponent(pv);
    splitterC.setSecondComponent(sv);
    splitterC.setProportion(0.5f);
  }
}

VerticalFlowLayout

This class extends and mimics FlowLayout, but layouts out components vertically instead of horizontally.
int hGap = 5;
int vGap = 5;
int hFill = true;
int vFill = false;
setLayout(new VerticalFlowLayout(VerticalFlowLayout.TOP, hGap, vGap, hFill, vFill));