001: /**
002: * L2FProd.com Common Components 7.3 License.
003: *
004: * Copyright 2005-2007 L2FProd.com
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */package com.l2fprod.common.propertysheet;
018:
019: import com.l2fprod.common.swing.IconPool;
020: import com.l2fprod.common.swing.LookAndFeelTweaks;
021: import com.l2fprod.common.swing.plaf.blue.BlueishButtonUI;
022: import com.l2fprod.common.util.ResourceManager;
023:
024: import java.awt.Dimension;
025: import java.awt.FlowLayout;
026: import java.awt.event.ActionEvent;
027: import java.beans.BeanInfo;
028: import java.beans.PropertyChangeEvent;
029: import java.beans.PropertyChangeListener;
030: import java.beans.PropertyDescriptor;
031: import java.util.Comparator;
032: import java.util.Iterator;
033: import java.util.Map;
034:
035: import javax.swing.AbstractAction;
036: import javax.swing.Action;
037: import javax.swing.BorderFactory;
038: import javax.swing.JEditorPane;
039: import javax.swing.JPanel;
040: import javax.swing.JScrollPane;
041: import javax.swing.JSplitPane;
042: import javax.swing.JToggleButton;
043: import javax.swing.UIManager;
044: import javax.swing.event.ListSelectionEvent;
045: import javax.swing.event.ListSelectionListener;
046:
047: /**
048: * An implementation of a PropertySheet which shows a table to
049: * edit/view values, a description pane which updates when the
050: * selection changes and buttons to toggle between a flat view and a
051: * by-category view of the properties. A button in the toolbar allows
052: * to sort the properties and categories by name.
053: * <p>
054: * Default sorting is by name (case-insensitive). Custom sorting can
055: * be implemented through
056: * {@link com.l2fprod.common.propertysheet.PropertySheetTableModel#setCategorySortingComparator(Comparator)}
057: * and
058: * {@link com.l2fprod.common.propertysheet.PropertySheetTableModel#setPropertySortingComparator(Comparator)}
059: */
060: public class PropertySheetPanel extends JPanel implements
061: PropertySheet, PropertyChangeListener {
062:
063: private PropertySheetTable table;
064: private PropertySheetTableModel model;
065: private JScrollPane tableScroll;
066: private ListSelectionListener selectionListener = new SelectionListener();
067:
068: private JPanel actionPanel;
069: private JToggleButton sortButton;
070: private JToggleButton asCategoryButton;
071: private JToggleButton descriptionButton;
072:
073: private JSplitPane split;
074: private int lastDescriptionHeight;
075:
076: private JEditorPane descriptionPanel;
077: private JScrollPane descriptionScrollPane;
078:
079: public PropertySheetPanel() {
080: this (new PropertySheetTable());
081: }
082:
083: public PropertySheetPanel(PropertySheetTable table) {
084: buildUI();
085: setTable(table);
086: }
087:
088: /**
089: * Sets the table used by this panel.
090: *
091: * Note: listeners previously added with
092: * {@link PropertySheetPanel#addPropertySheetChangeListener(PropertyChangeListener)}
093: * must be re-added after this call if the table model is not the
094: * same as the previous table.
095: *
096: * @param table
097: */
098: public void setTable(PropertySheetTable table) {
099: if (table == null) {
100: throw new IllegalArgumentException("table must not be null");
101: }
102:
103: // remove the property change listener from any previous model
104: if (model != null)
105: model.removePropertyChangeListener(this );
106:
107: // get the model from the table
108: model = (PropertySheetTableModel) table.getModel();
109: model.addPropertyChangeListener(this );
110:
111: // remove the listener from the old table
112: if (this .table != null)
113: this .table.getSelectionModel().removeListSelectionListener(
114: selectionListener);
115:
116: // prepare the new table
117: table.getSelectionModel().addListSelectionListener(
118: selectionListener);
119: tableScroll.getViewport().setView(table);
120:
121: // use the new table as our table
122: this .table = table;
123: }
124:
125: /**
126: * React to property changes by repainting.
127: */
128: public void propertyChange(PropertyChangeEvent evt) {
129: repaint();
130: }
131:
132: /**
133: * @return the table used to edit/view Properties.
134: */
135: public PropertySheetTable getTable() {
136: return table;
137: }
138:
139: /**
140: * Toggles the visibility of the description panel.
141: *
142: * @param visible
143: */
144: public void setDescriptionVisible(boolean visible) {
145: if (visible) {
146: add("Center", split);
147: split.setTopComponent(tableScroll);
148: split.setBottomComponent(descriptionScrollPane);
149: // restore the divider location
150: split.setDividerLocation(split.getHeight()
151: - lastDescriptionHeight);
152: } else {
153: // save the size of the description pane to restore it later
154: lastDescriptionHeight = split.getHeight()
155: - split.getDividerLocation();
156: remove(split);
157: add("Center", tableScroll);
158: }
159: descriptionButton.setSelected(visible);
160: PropertySheetPanel.this .revalidate();
161: }
162:
163: /**
164: * Toggles the visibility of the toolbar panel
165: *
166: * @param visible
167: */
168: public void setToolBarVisible(boolean visible) {
169: actionPanel.setVisible(visible);
170: PropertySheetPanel.this .revalidate();
171: }
172:
173: /**
174: * Set the current mode, either {@link PropertySheet#VIEW_AS_CATEGORIES}
175: * or {@link PropertySheet#VIEW_AS_FLAT_LIST}.
176: */
177: public void setMode(int mode) {
178: model.setMode(mode);
179: asCategoryButton
180: .setSelected(PropertySheet.VIEW_AS_CATEGORIES == mode);
181: }
182:
183: public void setProperties(Property[] properties) {
184: model.setProperties(properties);
185: }
186:
187: public Property[] getProperties() {
188: return model.getProperties();
189: }
190:
191: public void addProperty(Property property) {
192: model.addProperty(property);
193: }
194:
195: public void addProperty(int index, Property property) {
196: model.addProperty(index, property);
197: }
198:
199: public void removeProperty(Property property) {
200: model.removeProperty(property);
201: }
202:
203: public int getPropertyCount() {
204: return model.getPropertyCount();
205: }
206:
207: public Iterator propertyIterator() {
208: return model.propertyIterator();
209: }
210:
211: public void setBeanInfo(BeanInfo beanInfo) {
212: setProperties(beanInfo.getPropertyDescriptors());
213: }
214:
215: public void setProperties(PropertyDescriptor[] descriptors) {
216: Property[] properties = new Property[descriptors.length];
217: for (int i = 0, c = descriptors.length; i < c; i++) {
218: properties[i] = new PropertyDescriptorAdapter(
219: descriptors[i]);
220: }
221: setProperties(properties);
222: }
223:
224: /**
225: * Initializes the PropertySheet from the given object. If any, it cancels
226: * pending edit before proceeding with properties.
227: *
228: * @param data
229: */
230: public void readFromObject(Object data) {
231: // cancel pending edits
232: getTable().cancelEditing();
233:
234: Property[] properties = model.getProperties();
235: for (int i = 0, c = properties.length; i < c; i++) {
236: properties[i].readFromObject(data);
237: }
238: repaint();
239: }
240:
241: /**
242: * Writes the PropertySheet to the given object. If any, it commits pending
243: * edit before proceeding with properties.
244: *
245: * @param data
246: */
247: public void writeToObject(Object data) {
248: // ensure pending edits are committed
249: getTable().commitEditing();
250:
251: Property[] properties = getProperties();
252: for (int i = 0, c = properties.length; i < c; i++) {
253: properties[i].writeToObject(data);
254: }
255: }
256:
257: public void addPropertySheetChangeListener(
258: PropertyChangeListener listener) {
259: model.addPropertyChangeListener(listener);
260: }
261:
262: public void removePropertySheetChangeListener(
263: PropertyChangeListener listener) {
264: model.removePropertyChangeListener(listener);
265: }
266:
267: public void setEditorFactory(PropertyEditorFactory factory) {
268: table.setEditorFactory(factory);
269: }
270:
271: public PropertyEditorFactory getEditorFactory() {
272: return table.getEditorFactory();
273: }
274:
275: /**
276: * @deprecated use {@link #setEditorFactory(PropertyEditorFactory)}
277: * @param registry
278: */
279: public void setEditorRegistry(PropertyEditorRegistry registry) {
280: table.setEditorFactory(registry);
281: }
282:
283: /**
284: * @deprecated use {@link #getEditorFactory()}
285: */
286: public PropertyEditorRegistry getEditorRegistry() {
287: return (PropertyEditorRegistry) table.getEditorFactory();
288: }
289:
290: public void setRendererFactory(PropertyRendererFactory factory) {
291: table.setRendererFactory(factory);
292: }
293:
294: public PropertyRendererFactory getRendererFactory() {
295: return table.getRendererFactory();
296: }
297:
298: /**
299: * @deprecated use {@link #setRendererFactory(PropertyRendererFactory)}
300: * @param registry
301: */
302: public void setRendererRegistry(PropertyRendererRegistry registry) {
303: table.setRendererRegistry(registry);
304: }
305:
306: /**
307: * @deprecated use {@link #getRendererFactory()}
308: */
309: public PropertyRendererRegistry getRendererRegistry() {
310: return table.getRendererRegistry();
311: }
312:
313: /**
314: * Sets sorting of categories enabled or disabled.
315: *
316: * @param value true to enable sorting
317: */
318: public void setSortingCategories(boolean value) {
319: model.setSortingCategories(value);
320: sortButton.setSelected(isSorting());
321: }
322:
323: /**
324: * Is sorting of categories enabled.
325: *
326: * @return true if category sorting is enabled
327: */
328: public boolean isSortingCategories() {
329: return model.isSortingCategories();
330: }
331:
332: /**
333: * Sets sorting of properties enabled or disabled.
334: *
335: * @param value true to enable sorting
336: */
337: public void setSortingProperties(boolean value) {
338: model.setSortingProperties(value);
339: sortButton.setSelected(isSorting());
340: }
341:
342: /**
343: * Is sorting of properties enabled.
344: *
345: * @return true if property sorting is enabled
346: */
347: public boolean isSortingProperties() {
348: return model.isSortingProperties();
349: }
350:
351: /**
352: * Sets sorting properties and categories enabled or disabled.
353: *
354: * @param value true to enable sorting
355: */
356: public void setSorting(boolean value) {
357: model.setSortingCategories(value);
358: model.setSortingProperties(value);
359: sortButton.setSelected(value);
360: }
361:
362: /**
363: * @return true if properties or categories are sorted.
364: */
365: public boolean isSorting() {
366: return model.isSortingCategories()
367: || model.isSortingProperties();
368: }
369:
370: /**
371: * Sets the Comparator to be used with categories. Categories are
372: * treated as String-objects.
373: *
374: * @param comp java.util.Comparator used to compare categories
375: */
376: public void setCategorySortingComparator(Comparator comp) {
377: model.setCategorySortingComparator(comp);
378: }
379:
380: /**
381: * Sets the Comparator to be used with Property-objects.
382: *
383: * @param comp java.util.Comparator used to compare Property-objects
384: */
385: public void setPropertySortingComparator(Comparator comp) {
386: model.setPropertySortingComparator(comp);
387: }
388:
389: /**
390: * Set wether or not toggle states are restored when new properties are
391: * applied.
392: *
393: * @param value true to enable
394: */
395: public void setRestoreToggleStates(boolean value) {
396: model.setRestoreToggleStates(value);
397: }
398:
399: /**
400: * @return true is toggle state restore is enabled
401: */
402: public boolean isRestoreToggleStates() {
403: return model.isRestoreToggleStates();
404: }
405:
406: /**
407: * @return the category view toggle states.
408: */
409: public Map getToggleStates() {
410: return model.getToggleStates();
411: }
412:
413: /**
414: * Sets the toggle states for the category views. Note this <b>MUST</b> be
415: * called <b>BEFORE</b> setting any properties.
416: * @param toggleStates the toggle states as returned by getToggleStates
417: */
418: public void setToggleStates(Map toggleStates) {
419: model.setToggleStates(toggleStates);
420: }
421:
422: private void buildUI() {
423: LookAndFeelTweaks.setBorderLayout(this );
424: LookAndFeelTweaks.setBorder(this );
425:
426: actionPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 2,
427: 0));
428: actionPanel.setBorder(BorderFactory.createEmptyBorder(2, 0, 2,
429: 0));
430: actionPanel.setOpaque(false);
431: add("North", actionPanel);
432:
433: sortButton = new JToggleButton(new ToggleSortingAction());
434: sortButton.setUI(new BlueishButtonUI());
435: sortButton.setText(null);
436: sortButton.setOpaque(false);
437: actionPanel.add(sortButton);
438:
439: asCategoryButton = new JToggleButton(new ToggleModeAction());
440: asCategoryButton.setUI(new BlueishButtonUI());
441: asCategoryButton.setText(null);
442: asCategoryButton.setOpaque(false);
443: actionPanel.add(asCategoryButton);
444:
445: descriptionButton = new JToggleButton(
446: new ToggleDescriptionAction());
447: descriptionButton.setUI(new BlueishButtonUI());
448: descriptionButton.setText(null);
449: descriptionButton.setOpaque(false);
450: actionPanel.add(descriptionButton);
451:
452: split = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
453: split.setBorder(null);
454: split.setResizeWeight(1.0);
455: split.setContinuousLayout(true);
456: add("Center", split);
457:
458: tableScroll = new JScrollPane();
459: tableScroll.setBorder(BorderFactory.createEmptyBorder());
460: split.setTopComponent(tableScroll);
461:
462: descriptionPanel = new JEditorPane("text/html", "<html>");
463: descriptionPanel.setBorder(BorderFactory.createEmptyBorder());
464: descriptionPanel.setEditable(false);
465: descriptionPanel.setBackground(UIManager
466: .getColor("Panel.background"));
467: LookAndFeelTweaks.htmlize(descriptionPanel);
468:
469: selectionListener = new SelectionListener();
470:
471: descriptionScrollPane = new JScrollPane(descriptionPanel);
472: descriptionScrollPane.setBorder(LookAndFeelTweaks
473: .addMargin(BorderFactory.createLineBorder(UIManager
474: .getColor("controlDkShadow"))));
475: descriptionScrollPane.getViewport().setBackground(
476: descriptionPanel.getBackground());
477: descriptionScrollPane.setMinimumSize(new Dimension(50, 50));
478: split.setBottomComponent(descriptionScrollPane);
479:
480: // by default description is not visible, toolbar is visible.
481: setDescriptionVisible(false);
482: setToolBarVisible(true);
483: }
484:
485: class SelectionListener implements ListSelectionListener {
486:
487: public void valueChanged(ListSelectionEvent e) {
488: int row = table.getSelectedRow();
489: Property prop = null;
490: if (row >= 0 && table.getRowCount() > row)
491: prop = model.getPropertySheetElement(row).getProperty();
492: if (prop != null) {
493: descriptionPanel.setText("<html>"
494: + "<b>"
495: + (prop.getDisplayName() == null ? "" : prop
496: .getDisplayName())
497: + "</b><br>"
498: + (prop.getShortDescription() == null ? ""
499: : prop.getShortDescription()));
500: } else {
501: descriptionPanel.setText("<html>");
502: }
503:
504: //position it at the top
505: descriptionPanel.setCaretPosition(0);
506: }
507: }
508:
509: class ToggleModeAction extends AbstractAction {
510:
511: public ToggleModeAction() {
512: super ("toggle", IconPool.shared().get(
513: PropertySheet.class
514: .getResource("icons/category.gif")));
515: putValue(Action.SHORT_DESCRIPTION, ResourceManager.get(
516: PropertySheet.class).getString(
517: "PropertySheetPanel.category.shortDescription"));
518: }
519:
520: public void actionPerformed(ActionEvent e) {
521: if (asCategoryButton.isSelected()) {
522: model.setMode(PropertySheet.VIEW_AS_CATEGORIES);
523: } else {
524: model.setMode(PropertySheet.VIEW_AS_FLAT_LIST);
525: }
526: }
527: }
528:
529: class ToggleDescriptionAction extends AbstractAction {
530:
531: public ToggleDescriptionAction() {
532: super ("toggleDescription", IconPool.shared().get(
533: PropertySheet.class
534: .getResource("icons/description.gif")));
535: putValue(Action.SHORT_DESCRIPTION, ResourceManager.get(
536: PropertySheet.class).getString(
537: "PropertySheetPanel.description.shortDescription"));
538: }
539:
540: public void actionPerformed(ActionEvent e) {
541: setDescriptionVisible(descriptionButton.isSelected());
542: }
543: }
544:
545: class ToggleSortingAction extends AbstractAction {
546:
547: public ToggleSortingAction() {
548: super ("toggleSorting", IconPool.shared().get(
549: PropertySheet.class.getResource("icons/sort.gif")));
550: putValue(Action.SHORT_DESCRIPTION, ResourceManager.get(
551: PropertySheet.class).getString(
552: "PropertySheetPanel.sort.shortDescription"));
553: }
554:
555: public void actionPerformed(ActionEvent e) {
556: setSorting(sortButton.isSelected());
557: }
558: }
559:
560: }
|