package com.limegroup.gnutella.gui.xml; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.util.Arrays; import java.util.List; import javax.swing.Box; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.text.Document; import javax.swing.undo.UndoManager; import com.limegroup.gnutella.Assert; import com.limegroup.gnutella.gui.ClearableAutoCompleteTextField; import com.limegroup.gnutella.gui.KeyProcessingTextField; import com.limegroup.gnutella.gui.GUIMediator; import com.limegroup.gnutella.gui.search.SearchField; import com.limegroup.gnutella.xml.LimeXMLSchema; import com.limegroup.gnutella.xml.SchemaFieldInfo; import com.limegroup.gnutella.util.NameValue; /** * A panel that displays fields of an XML schema. */ public abstract class IndentingPanel extends JPanel implements Scrollable { /** * The property that contains the canonical key of the field * in each JComponent. */ private static final String KEY_PROP = "lime.canonKey"; /** * Property for whether or not this is a default entry. */ private static final String DEFAULT_PROP = "lime.defaultField"; /** * The number of fields to show by default. */ private static final int DEFAULT_FIELDS = 5; /** * The first TextField for input, used to quickly set the focus on * the text field when this panel wants focus. */ private KeyProcessingTextField keyProcessingTextField; // Constructor public IndentingPanel(LimeXMLSchema schema, ActionListener listener, Document document, UndoManager undoer, boolean expand, boolean indent, boolean searching) { this.setOpaque(false); int fieldsToShow = expand ? schema.getCanonicalizedFields().size() : DEFAULT_FIELDS; List fields = schema.getCanonicalizedFields(); boolean defaultField = true; int added = 0; setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.WEST; c.gridwidth = GridBagConstraints.REMAINDER; for (int i = 0; i < fields.size(); i++) { SchemaFieldInfo infoField = (SchemaFieldInfo)fields.get(i); if(!searching && !infoField.isEditable()) continue; if(added == fieldsToShow) { addMoreOptions(c); defaultField = false; } JComponent comp = addField(schema, infoField, c, defaultField, searching); // remember the first KeyProcessingTextField we add. if(comp instanceof KeyProcessingTextField) { KeyProcessingTextField kptf = (KeyProcessingTextField)comp; if(keyProcessingTextField == null) { keyProcessingTextField = kptf; if(document != null) kptf.setDocument(document); if(undoer != null) kptf.setUndoManager(undoer); } kptf.addActionListener(listener); } added++; } c.weightx = 1; c.weighty = 1; c.fill = GridBagConstraints.NONE; add(Box.createGlue(), c); setFieldsVisible(false); } /** * Adds a 'more options' listener. */ public void addMoreOptionsListener(ActionListener e) { for(int i = 0; i < getComponentCount(); i++) { Component c = getComponent(i); if (c instanceof JCheckBox) ((JCheckBox)c).addActionListener(e); } } /** * Creates the more options checkbox. */ private void addMoreOptions(GridBagConstraints c) { JCheckBox moreOptions = new JCheckBox(GUIMediator.getStringResource("XML_SEARCH_MORE_OPTIONS_LABEL")); moreOptions.setOpaque(false); moreOptions.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JCheckBox source = (JCheckBox)e.getSource(); setFieldsVisible(source.isSelected()); invalidate(); revalidate(); } }); c.insets = new Insets(2, 0, 5, 0); c.fill = GridBagConstraints.NONE; add(moreOptions, c); } /** * Gets a new TextField of the correct variety. */ private JComponent getTextField(boolean searching) { return searching ? new SearchField(14) : new ClearableAutoCompleteTextField(30); } /** * Gets a combo box with the correct entries. */ private JComboBox getOptions(SchemaFieldInfo infoField) { List values = infoField.getEnumerationList(); int d = values.size(); ComboBoxValue[] vals = new ComboBoxValue[d + 1]; vals[0] = new ComboBoxValue("", ""); for (int m = 0; m < d; m++) vals[m + 1] = new ComboBoxValue((NameValue)values.get(m)); Arrays.sort(vals); JComboBox comboBox = new JComboBox(vals); comboBox.setOpaque(false); return comboBox; } private JComponent addField(LimeXMLSchema schema, SchemaFieldInfo infoField, GridBagConstraints c, boolean defaultField, boolean searching) { String currField = infoField.getCanonicalizedFieldName(); String fieldName = XMLUtils.getResource(currField); JLabel label = new JLabel(fieldName); c.insets = new Insets(0, 0, 3, 0); c.fill = GridBagConstraints.HORIZONTAL; add(label, c); JComponent comp = null; switch(infoField.getFieldType()) { case SchemaFieldInfo.TEXTFIELD: comp = getTextField(searching); break; case SchemaFieldInfo.OPTIONS: comp = getOptions(infoField); break; default: Assert.that(false, "bad type: " + infoField.getFieldType() + ", name: " + currField); } c.insets = new Insets(0, 0, 5, 0); add(comp, c); comp.putClientProperty(KEY_PROP, currField); if(!defaultField) { label.putClientProperty(DEFAULT_PROP, Boolean.FALSE); comp.putClientProperty(DEFAULT_PROP, Boolean.FALSE); } return comp; } /** * Iterates through fields and sets ones that aren't default to visible * or invisible. */ private void setFieldsVisible(boolean viz) { for(int i = 0; i < getComponentCount(); i++) { Component c = getComponent(i); if (c instanceof JComponent) { Object key = ((JComponent)c).getClientProperty(DEFAULT_PROP); if(key == Boolean.FALSE) c.setVisible(viz); } } } /** * Requests focus for the focusRequestor instead of this. */ public void requestFirstFocus() { if(keyProcessingTextField != null) keyProcessingTextField.requestFocus(); else super.requestFocus(); } /** * Returns the field which key processes should be forwarded to. */ public KeyProcessingTextField getFirstTextField() { return keyProcessingTextField; } /** * Gets the JComponent that has the correct fieldname. */ protected JComponent getField(String fieldName) { for(int i = 0; i < getComponentCount(); i++) { Component c = getComponent(i); if (c instanceof JComponent) { Object key = ((JComponent)c).getClientProperty(KEY_PROP); if(key != null && key.equals(fieldName)) return (JComponent)c; } } return null; } /** * Clears all fields in this. */ public void clear() { for(int i = 0; i < getComponentCount(); i++) clearField(getComponent(i)); } /** * Clears a single component. */ protected void clearField(Component c) { if(c instanceof JTextField) ((JTextField)c).setText(null); else if (c instanceof JComboBox) ((JComboBox)c).setSelectedIndex(0); } /** * Implement a Scrollable interface to properly scroll by some * sane amount and not pixels. */ public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { switch (orientation) { case SwingConstants.HORIZONTAL: return visibleRect.width / 10; case SwingConstants.VERTICAL: return visibleRect.height / 10; default: throw new IllegalArgumentException("Unknown orientation " + orientation); } } public int getScrollableBlockIncrement( Rectangle visibleRect, int orientation, int direction) { switch (orientation) { case SwingConstants.HORIZONTAL: return visibleRect.width; case SwingConstants.VERTICAL: return visibleRect.height; default: throw new IllegalArgumentException("Unknown orientation " + orientation); } } public boolean getScrollableTracksViewportWidth() { return false; } public boolean getScrollableTracksViewportHeight() { return false; } }