/**
* L2FProd.com Common Components 7.3 License.
*
* Copyright 2005-2007 L2FProd.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.l2fprod.common.propertysheet;
import com.l2fprod.common.swing.IconPool;
import com.l2fprod.common.swing.LookAndFeelTweaks;
import com.l2fprod.common.swing.plaf.blue.BlueishButtonUI;
import com.l2fprod.common.util.ResourceManager;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.beans.BeanInfo;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
/**
* An implementation of a PropertySheet which shows a table to
* edit/view values, a description pane which updates when the
* selection changes and buttons to toggle between a flat view and a
* by-category view of the properties. A button in the toolbar allows
* to sort the properties and categories by name.
* <p/>
* Default sorting is by name (case-insensitive). Custom sorting can
* be implemented through
* {@link com.l2fprod.common.propertysheet.PropertySheetTableModel#setCategorySortingComparator(Comparator)}
* and
* {@link com.l2fprod.common.propertysheet.PropertySheetTableModel#setPropertySortingComparator(Comparator)}
*/
public class PropertySheetPanel extends JPanel implements PropertySheet, PropertyChangeListener {
private PropertySheetTable table;
private PropertySheetTableModel model;
private JScrollPane tableScroll;
private ListSelectionListener selectionListener = new SelectionListener();
private JPanel actionPanel;
private JToggleButton sortButton;
private JToggleButton asCategoryButton;
private JToggleButton descriptionButton;
private JSplitPane split;
private int lastDescriptionHeight;
private JEditorPane descriptionPanel;
private JScrollPane descriptionScrollPane;
public PropertySheetPanel() {
this(new PropertySheetTable());
}
public PropertySheetPanel(PropertySheetTable table) {
buildUI();
setTable(table);
}
/**
* Sets the table used by this panel.
* <p/>
* Note: listeners previously added with
* {@link PropertySheetPanel#addPropertySheetChangeListener(PropertyChangeListener)}
* must be re-added after this call if the table model is not the
* same as the previous table.
*
* @param table
*/
public void setTable(PropertySheetTable table) {
if (table == null) {
throw new IllegalArgumentException("table must not be null");
}
// remove the property change listener from any previous model
if (model != null)
model.removePropertyChangeListener(this);
// get the model from the table
model = (PropertySheetTableModel) table.getModel();
model.addPropertyChangeListener(this);
// remove the listener from the old table
if (this.table != null)
this.table.getSelectionModel().removeListSelectionListener(selectionListener);
// prepare the new table
table.getSelectionModel().addListSelectionListener(selectionListener);
tableScroll.getViewport().setView(table);
// use the new table as our table
this.table = table;
}
/**
* React to property changes by repainting.
*/
public void propertyChange(PropertyChangeEvent evt) {
repaint();
}
/**
* @return the table used to edit/view Properties.
*/
public PropertySheetTable getTable() {
return table;
}
/**
* Toggles the visibility of the description panel.
*
* @param visible
*/
public void setDescriptionVisible(boolean visible) {
if (visible) {
add("Center", split);
split.setTopComponent(tableScroll);
split.setBottomComponent(descriptionScrollPane);
// restore the divider location
split.setDividerLocation(split.getHeight() - lastDescriptionHeight);
} else {
// save the size of the description pane to restore it later
lastDescriptionHeight = split.getHeight() - split.getDividerLocation();
remove(split);
add("Center", tableScroll);
}
descriptionButton.setSelected(visible);
PropertySheetPanel.this.revalidate();
}
/**
* Toggles the visibility of the toolbar panel
*
* @param visible
*/
public void setToolBarVisible(boolean visible) {
actionPanel.setVisible(visible);
PropertySheetPanel.this.revalidate();
}
/**
* Set the current mode, either {@link PropertySheet#VIEW_AS_CATEGORIES}
* or {@link PropertySheet#VIEW_AS_FLAT_LIST}.
*/
public void setMode(int mode) {
model.setMode(mode);
asCategoryButton.setSelected(PropertySheet.VIEW_AS_CATEGORIES == mode);
}
public void setProperties(Property[] properties) {
model.setProperties(properties);
}
public Property[] getProperties() {
return model.getProperties();
}
public void addProperty(Property property) {
model.addProperty(property);
}
public void addProperty(int index, Property property) {
model.addProperty(index, property);
}
public void removeProperty(Property property) {
model.removeProperty(property);
}
public int getPropertyCount() {
return model.getPropertyCount();
}
public Iterator propertyIterator() {
return model.propertyIterator();
}
public void setBeanInfo(BeanInfo beanInfo) {
setProperties(beanInfo.getPropertyDescriptors());
}
public void setProperties(PropertyDescriptor[] descriptors) {
Property[] properties = new Property[descriptors.length];
for (int i = 0, c = descriptors.length; i < c; i++) {
properties[i] = new PropertyDescriptorAdapter(descriptors[i]);
}
setProperties(properties);
}
/**
* Initializes the PropertySheet from the given object. If any, it cancels
* pending edit before proceeding with properties.
*
* @param data
*/
public void readFromObject(Object data) {
// cancel pending edits
getTable().cancelEditing();
Property[] properties = model.getProperties();
for (int i = 0, c = properties.length; i < c; i++) {
properties[i].readFromObject(data);
}
repaint();
}
/**
* Writes the PropertySheet to the given object. If any, it commits pending
* edit before proceeding with properties.
*
* @param data
*/
public void writeToObject(Object data) {
// ensure pending edits are committed
getTable().commitEditing();
Property[] properties = getProperties();
for (int i = 0, c = properties.length; i < c; i++) {
properties[i].writeToObject(data);
}
}
public void addPropertySheetChangeListener(PropertyChangeListener listener) {
model.addPropertyChangeListener(listener);
}
public void removePropertySheetChangeListener(PropertyChangeListener listener) {
model.removePropertyChangeListener(listener);
}
public void setEditorFactory(PropertyEditorFactory factory) {
table.setEditorFactory(factory);
}
public PropertyEditorFactory getEditorFactory() {
return table.getEditorFactory();
}
/**
* @param registry
* @deprecated use {@link #setEditorFactory(PropertyEditorFactory)}
*/
public void setEditorRegistry(PropertyEditorRegistry registry) {
table.setEditorFactory(registry);
}
/**
* @deprecated use {@link #getEditorFactory()}
*/
public PropertyEditorRegistry getEditorRegistry() {
return (PropertyEditorRegistry) table.getEditorFactory();
}
public void setRendererFactory(PropertyRendererFactory factory) {
table.setRendererFactory(factory);
}
public PropertyRendererFactory getRendererFactory() {
return table.getRendererFactory();
}
/**
* @param registry
* @deprecated use {@link #setRendererFactory(PropertyRendererFactory)}
*/
public void setRendererRegistry(PropertyRendererRegistry registry) {
table.setRendererRegistry(registry);
}
/**
* @deprecated use {@link #getRendererFactory()}
*/
public PropertyRendererRegistry getRendererRegistry() {
return table.getRendererRegistry();
}
/**
* Sets sorting of categories enabled or disabled.
*
* @param value true to enable sorting
*/
public void setSortingCategories(boolean value) {
model.setSortingCategories(value);
sortButton.setSelected(isSorting());
}
/**
* Is sorting of categories enabled.
*
* @return true if category sorting is enabled
*/
public boolean isSortingCategories() {
return model.isSortingCategories();
}
/**
* Sets sorting of properties enabled or disabled.
*
* @param value true to enable sorting
*/
public void setSortingProperties(boolean value) {
model.setSortingProperties(value);
sortButton.setSelected(isSorting());
}
/**
* Is sorting of properties enabled.
*
* @return true if property sorting is enabled
*/
public boolean isSortingProperties() {
return model.isSortingProperties();
}
/**
* Sets sorting properties and categories enabled or disabled.
*
* @param value true to enable sorting
*/
public void setSorting(boolean value) {
model.setSortingCategories(value);
model.setSortingProperties(value);
sortButton.setSelected(value);
}
/**
* @return true if properties or categories are sorted.
*/
public boolean isSorting() {
return model.isSortingCategories() || model.isSortingProperties();
}
/**
* Sets the Comparator to be used with categories. Categories are
* treated as String-objects.
*
* @param comp java.util.Comparator used to compare categories
*/
public void setCategorySortingComparator(Comparator comp) {
model.setCategorySortingComparator(comp);
}
/**
* Sets the Comparator to be used with Property-objects.
*
* @param comp java.util.Comparator used to compare Property-objects
*/
public void setPropertySortingComparator(Comparator comp) {
model.setPropertySortingComparator(comp);
}
/**
* Set wether or not toggle states are restored when new properties are
* applied.
*
* @param value true to enable
*/
public void setRestoreToggleStates(boolean value) {
model.setRestoreToggleStates(value);
}
/**
* @return true is toggle state restore is enabled
*/
public boolean isRestoreToggleStates() {
return model.isRestoreToggleStates();
}
/**
* @return the category view toggle states.
*/
public Map getToggleStates() {
return model.getToggleStates();
}
/**
* Sets the toggle states for the category views. Note this <b>MUST</b> be
* called <b>BEFORE</b> setting any properties.
*
* @param toggleStates the toggle states as returned by getToggleStates
*/
public void setToggleStates(Map toggleStates) {
model.setToggleStates(toggleStates);
}
private void buildUI() {
LookAndFeelTweaks.setBorderLayout(this);
LookAndFeelTweaks.setBorder(this);
actionPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 2, 0));
actionPanel.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));
actionPanel.setOpaque(false);
add("North", actionPanel);
sortButton = new JToggleButton(new ToggleSortingAction());
sortButton.setUI(new BlueishButtonUI());
sortButton.setText(null);
sortButton.setOpaque(false);
actionPanel.add(sortButton);
asCategoryButton = new JToggleButton(new ToggleModeAction());
asCategoryButton.setUI(new BlueishButtonUI());
asCategoryButton.setText(null);
asCategoryButton.setOpaque(false);
actionPanel.add(asCategoryButton);
descriptionButton = new JToggleButton(new ToggleDescriptionAction());
descriptionButton.setUI(new BlueishButtonUI());
descriptionButton.setText(null);
descriptionButton.setOpaque(false);
actionPanel.add(descriptionButton);
split = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
split.setBorder(null);
split.setResizeWeight(1.0);
split.setContinuousLayout(true);
add("Center", split);
tableScroll = new JScrollPane();
tableScroll.setBorder(BorderFactory.createEmptyBorder());
split.setTopComponent(tableScroll);
descriptionPanel = new JEditorPane("text/html", "<html>");
descriptionPanel.setBorder(BorderFactory.createEmptyBorder());
descriptionPanel.setEditable(false);
descriptionPanel.setBackground(UIManager.getColor("Panel.background"));
LookAndFeelTweaks.htmlize(descriptionPanel);
selectionListener = new SelectionListener();
descriptionScrollPane = new JScrollPane(descriptionPanel);
descriptionScrollPane.setBorder(LookAndFeelTweaks.addMargin(BorderFactory.createLineBorder(UIManager.getColor("controlDkShadow"))));
descriptionScrollPane.getViewport().setBackground(descriptionPanel.getBackground());
descriptionScrollPane.setMinimumSize(new Dimension(50, 50));
split.setBottomComponent(descriptionScrollPane);
// by default description is not visible, toolbar is visible.
setDescriptionVisible(false);
setToolBarVisible(true);
}
class SelectionListener implements ListSelectionListener {
public void valueChanged(ListSelectionEvent e) {
int row = table.getSelectedRow();
Property prop = null;
if (row >= 0 && table.getRowCount() > row)
prop = model.getPropertySheetElement(row).getProperty();
if (prop != null) {
descriptionPanel.setText("<html>" + "<b>" + (prop.getDisplayName() == null ? "" : prop.getDisplayName()) + "</b><br>" +
(prop.getShortDescription() == null ? "" : prop.getShortDescription()));
} else {
descriptionPanel.setText("<html>");
}
//position it at the top
descriptionPanel.setCaretPosition(0);
}
}
class ToggleModeAction extends AbstractAction {
public ToggleModeAction() {
super("toggle", IconPool.shared().get(PropertySheet.class.getResource("icons/category.gif")));
putValue(Action.SHORT_DESCRIPTION,
ResourceManager.get(PropertySheet.class).getString("PropertySheetPanel.category.shortDescription"));
}
public void actionPerformed(ActionEvent e) {
if (asCategoryButton.isSelected()) {
model.setMode(PropertySheet.VIEW_AS_CATEGORIES);
} else {
model.setMode(PropertySheet.VIEW_AS_FLAT_LIST);
}
}
}
class ToggleDescriptionAction extends AbstractAction {
public ToggleDescriptionAction() {
super("toggleDescription", IconPool.shared().get(PropertySheet.class.getResource("icons/description.gif")));
putValue(Action.SHORT_DESCRIPTION,
ResourceManager.get(PropertySheet.class).getString("PropertySheetPanel.tooltip.shortDescription"));
}
public void actionPerformed(ActionEvent e) {
setDescriptionVisible(descriptionButton.isSelected());
}
}
class ToggleSortingAction extends AbstractAction {
public ToggleSortingAction() {
super("toggleSorting", IconPool.shared().get(PropertySheet.class.getResource("icons/sort.gif")));
putValue(Action.SHORT_DESCRIPTION, ResourceManager.get(PropertySheet.class).getString("PropertySheetPanel.sort.shortDescription"));
}
public void actionPerformed(ActionEvent e) {
setSorting(sortButton.isSelected());
}
}
}