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.ObjectTableModel;
020:
021: import java.beans.PropertyChangeEvent;
022: import java.beans.PropertyChangeListener;
023: import java.beans.PropertyChangeSupport;
024: import java.util.ArrayList;
025: import java.util.Arrays;
026: import java.util.Collections;
027: import java.util.Comparator;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032:
033: import javax.swing.table.AbstractTableModel;
034:
035: /**
036: * PropertySheetTableModel. <br>
037: *
038: */
039: public class PropertySheetTableModel extends AbstractTableModel
040: implements PropertyChangeListener, PropertySheet,
041: ObjectTableModel {
042: public static final int NAME_COLUMN = 0;
043: public static final int VALUE_COLUMN = 1;
044: public static final int NUM_COLUMNS = 2;
045:
046: private PropertyChangeSupport listeners = new PropertyChangeSupport(
047: this );
048: private List model;
049: private List publishedModel;
050: private List properties;
051: private int mode;
052: private boolean sortingCategories;
053: private boolean sortingProperties;
054: private boolean restoreToggleStates;
055: private Comparator categorySortingComparator;
056: private Comparator propertySortingComparator;
057: private Map toggleStates;
058:
059: public PropertySheetTableModel() {
060: model = new ArrayList();
061: publishedModel = new ArrayList();
062: properties = new ArrayList();
063: mode = PropertySheet.VIEW_AS_FLAT_LIST;
064: sortingCategories = false;
065: sortingProperties = false;
066: restoreToggleStates = false;
067: toggleStates = new HashMap();
068: }
069:
070: /* (non-Javadoc)
071: * @see com.l2fprod.common.propertysheet.PropertySheet#setProperties(com.l2fprod.common.propertysheet.Property[])
072: */
073: public void setProperties(Property[] newProperties) {
074: // unregister the listeners from previous properties
075: for (Iterator iter = properties.iterator(); iter.hasNext();) {
076: Property prop = (Property) iter.next();
077: prop.removePropertyChangeListener(this );
078: }
079:
080: // replace the current properties
081: properties.clear();
082: properties.addAll(Arrays.asList(newProperties));
083:
084: // add listeners
085: for (Iterator iter = properties.iterator(); iter.hasNext();) {
086: Property prop = (Property) iter.next();
087: prop.addPropertyChangeListener(this );
088: }
089:
090: buildModel();
091: }
092:
093: /* (non-Javadoc)
094: * @see com.l2fprod.common.propertysheet.PropertySheet#getProperties()
095: */
096: public Property[] getProperties() {
097: return (Property[]) properties.toArray(new Property[properties
098: .size()]);
099: }
100:
101: /* (non-Javadoc)
102: * @see com.l2fprod.common.propertysheet.PropertySheet#addProperty(com.l2fprod.common.propertysheet.Property)
103: */
104: public void addProperty(Property property) {
105: properties.add(property);
106: property.addPropertyChangeListener(this );
107: buildModel();
108: }
109:
110: /* (non-Javadoc)
111: * @see com.l2fprod.common.propertysheet.PropertySheet#addProperty(int, com.l2fprod.common.propertysheet.Property)
112: */
113: public void addProperty(int index, Property property) {
114: properties.add(index, property);
115: property.addPropertyChangeListener(this );
116: buildModel();
117: }
118:
119: /* (non-Javadoc)
120: * @see com.l2fprod.common.propertysheet.PropertySheet#removeProperty(com.l2fprod.common.propertysheet.Property)
121: */
122: public void removeProperty(Property property) {
123: properties.remove(property);
124: property.removePropertyChangeListener(this );
125: buildModel();
126: }
127:
128: /* (non-Javadoc)
129: * @see com.l2fprod.common.propertysheet.PropertySheet#getPropertyCount()
130: */
131: public int getPropertyCount() {
132: return properties.size();
133: }
134:
135: /* (non-Javadoc)
136: * @see com.l2fprod.common.propertysheet.PropertySheet#propertyIterator()
137: */
138: public Iterator propertyIterator() {
139: return properties.iterator();
140: }
141:
142: /**
143: * Set the current mode, either {@link PropertySheet#VIEW_AS_CATEGORIES}
144: * or {@link PropertySheet#VIEW_AS_FLAT_LIST}.
145: */
146: public void setMode(int mode) {
147: if (this .mode == mode) {
148: return;
149: }
150: this .mode = mode;
151: buildModel();
152: }
153:
154: /**
155: * Get the current mode, either {@link PropertySheet#VIEW_AS_CATEGORIES}
156: * or {@link PropertySheet#VIEW_AS_FLAT_LIST}.
157: */
158: public int getMode() {
159: return mode;
160: }
161:
162: /* (non-Javadoc)
163: * @see javax.swing.table.TableModel#getColumnClass(int)
164: */
165: public Class getColumnClass(int columnIndex) {
166: return super .getColumnClass(columnIndex);
167: }
168:
169: /* (non-Javadoc)
170: * @see javax.swing.table.TableModel#getColumnCount()
171: */
172: public int getColumnCount() {
173: return NUM_COLUMNS;
174: }
175:
176: /* (non-Javadoc)
177: * @see javax.swing.table.TableModel#getRowCount()
178: */
179: public int getRowCount() {
180: return publishedModel.size();
181: }
182:
183: /* (non-Javadoc)
184: * @see com.l2fprod.common.swing.ObjectTableModel#getObject(int)
185: */
186: public Object getObject(int rowIndex) {
187: return getPropertySheetElement(rowIndex);
188: }
189:
190: /**
191: * Get the current property sheet element, of type {@link Item}, at
192: * the specified row.
193: */
194: public Item getPropertySheetElement(int rowIndex) {
195: return (Item) publishedModel.get(rowIndex);
196: }
197:
198: /**
199: * Get whether this model is currently sorting categories.
200: */
201: public boolean isSortingCategories() {
202: return sortingCategories;
203: }
204:
205: /**
206: * Set whether this model is currently sorting categories.
207: * If this changes the sorting, the model will be rebuilt.
208: */
209: public void setSortingCategories(boolean value) {
210: boolean old = sortingCategories;
211: sortingCategories = value;
212: if (sortingCategories != old)
213: buildModel();
214: }
215:
216: /**
217: * Get whether this model is currently sorting properties.
218: */
219: public boolean isSortingProperties() {
220: return sortingProperties;
221: }
222:
223: /**
224: * Set whether this model is currently sorting properties.
225: * If this changes the sorting, the model will be rebuilt.
226: */
227: public void setSortingProperties(boolean value) {
228: boolean old = sortingProperties;
229: sortingProperties = value;
230: if (sortingProperties != old)
231: buildModel();
232: }
233:
234: /**
235: * Set the comparator used for sorting categories. If this
236: * changes the comparator, the model will be rebuilt.
237: */
238: public void setCategorySortingComparator(Comparator comp) {
239: Comparator old = categorySortingComparator;
240: categorySortingComparator = comp;
241: if (categorySortingComparator != old)
242: buildModel();
243: }
244:
245: /**
246: * Set the comparator used for sorting properties. If this
247: * changes the comparator, the model will be rebuilt.
248: */
249: public void setPropertySortingComparator(Comparator comp) {
250: Comparator old = propertySortingComparator;
251: propertySortingComparator = comp;
252: if (propertySortingComparator != old)
253: buildModel();
254: }
255:
256: /**
257: * Set whether or not this model will restore the toggle states when new
258: * properties are applied.
259: */
260: public void setRestoreToggleStates(boolean value) {
261: restoreToggleStates = value;
262: if (!restoreToggleStates) {
263: toggleStates.clear();
264: }
265: }
266:
267: /**
268: * Get whether this model is restoring toggle states
269: */
270: public boolean isRestoreToggleStates() {
271: return restoreToggleStates;
272: }
273:
274: /**
275: * @return the category view toggle states.
276: */
277: public Map getToggleStates() {
278: // Call visibilityChanged to populate the toggleStates map
279: visibilityChanged(restoreToggleStates);
280: return toggleStates;
281: }
282:
283: /**
284: * Sets the toggle states for the category views. Note this <b>MUST</b> be
285: * called <b>BEFORE</b> setting any properties.
286: * @param toggleStates the toggle states as returned by getToggleStates
287: */
288: public void setToggleStates(Map toggleStates) {
289: // We are providing a toggleStates map - so by definition we must want to
290: // store the toggle states
291: setRestoreToggleStates(true);
292: this .toggleStates.clear();
293: this .toggleStates.putAll(toggleStates);
294: }
295:
296: /**
297: * Retrieve the value at the specified row and column location.
298: * When the row contains a category or the column is
299: * {@link #NAME_COLUMN}, an {@link Item} object will be returned.
300: * If the row is a property and the column is {@link #VALUE_COLUMN},
301: * the value of the property will be returned.
302: *
303: * @see javax.swing.table.TableModel#getValueAt(int, int)
304: */
305: public Object getValueAt(int rowIndex, int columnIndex) {
306: Object result = null;
307: Item item = getPropertySheetElement(rowIndex);
308:
309: if (item.isProperty()) {
310: switch (columnIndex) {
311: case NAME_COLUMN:
312: result = item;
313: break;
314:
315: case VALUE_COLUMN:
316: try {
317: result = item.getProperty().getValue();
318: } catch (Exception e) {
319: e.printStackTrace();
320: }
321: break;
322:
323: default:
324: // should not happen
325: }
326: } else {
327: result = item;
328: }
329: return result;
330: }
331:
332: /**
333: * Sets the value at the specified row and column. This will have
334: * no effect unless the row is a property and the column is
335: * {@link #VALUE_COLUMN}.
336: *
337: * @see javax.swing.table.TableModel#setValueAt(java.lang.Object, int, int)
338: */
339: public void setValueAt(Object value, int rowIndex, int columnIndex) {
340: Item item = getPropertySheetElement(rowIndex);
341: if (item.isProperty()) {
342: if (columnIndex == VALUE_COLUMN) {
343: try {
344: item.getProperty().setValue(value);
345: } catch (Exception e) {
346: e.printStackTrace();
347: }
348: }
349: }
350: }
351:
352: /**
353: * Add a {@link PropertyChangeListener} to the current model.
354: */
355: public void addPropertyChangeListener(
356: PropertyChangeListener listener) {
357: listeners.addPropertyChangeListener(listener);
358: }
359:
360: public void removePropertyChangeListener(
361: PropertyChangeListener listener) {
362: listeners.removePropertyChangeListener(listener);
363: }
364:
365: public void propertyChange(PropertyChangeEvent evt) {
366: // forward the event to registered listeners
367: listeners.firePropertyChange(evt);
368: }
369:
370: protected void visibilityChanged(final boolean restoreOldStates) {
371: // Store the old visibility states
372: if (restoreOldStates) {
373: for (Iterator iter = publishedModel.iterator(); iter
374: .hasNext();) {
375: final Item item = (Item) iter.next();
376: toggleStates
377: .put(item.getKey(),
378: item.isVisible() ? Boolean.TRUE
379: : Boolean.FALSE);
380: }
381: }
382: publishedModel.clear();
383: for (Iterator iter = model.iterator(); iter.hasNext();) {
384: Item item = (Item) iter.next();
385: Item parent = item.getParent();
386: if (restoreOldStates) {
387: Boolean oldState = (Boolean) toggleStates.get(item
388: .getKey());
389: if (oldState != null) {
390: item.setVisible(oldState.booleanValue());
391: }
392: if (parent != null) {
393: oldState = (Boolean) toggleStates.get(parent
394: .getKey());
395: if (oldState != null) {
396: parent.setVisible(oldState.booleanValue());
397: }
398: }
399: }
400: if (parent == null || parent.isVisible())
401: publishedModel.add(item);
402: }
403: }
404:
405: private void buildModel() {
406: model.clear();
407:
408: if (properties != null && properties.size() > 0) {
409: List sortedProperties = sortProperties(properties);
410:
411: switch (mode) {
412: case PropertySheet.VIEW_AS_FLAT_LIST:
413: // just add all the properties without categories
414: addPropertiesToModel(sortedProperties, null);
415: break;
416:
417: case PropertySheet.VIEW_AS_CATEGORIES: {
418: // add properties by category
419: List categories = sortCategories(getPropertyCategories(sortedProperties));
420:
421: for (Iterator iter = categories.iterator(); iter
422: .hasNext();) {
423: String category = (String) iter.next();
424: Item categoryItem = new Item(category, null);
425: model.add(categoryItem);
426: addPropertiesToModel(
427: sortProperties(getPropertiesForCategory(
428: properties, category)),
429: categoryItem);
430: }
431: break;
432: }
433:
434: default:
435: // should not happen
436: }
437: }
438:
439: visibilityChanged(restoreToggleStates);
440: fireTableDataChanged();
441: }
442:
443: protected List sortProperties(List localProperties) {
444: List sortedProperties = new ArrayList(localProperties);
445: if (sortingProperties) {
446: if (propertySortingComparator == null) {
447: // if no comparator was defined by the user, use the default
448: propertySortingComparator = new PropertyComparator();
449: }
450: Collections.sort(sortedProperties,
451: propertySortingComparator);
452: }
453: return sortedProperties;
454: }
455:
456: protected List sortCategories(List localCategories) {
457: List sortedCategories = new ArrayList(localCategories);
458: if (sortingCategories) {
459: if (categorySortingComparator == null) {
460: // if no comparator was defined by the user, use the default
461: categorySortingComparator = STRING_COMPARATOR;
462: }
463: Collections.sort(sortedCategories,
464: categorySortingComparator);
465: }
466: return sortedCategories;
467: }
468:
469: protected List getPropertyCategories(List localProperties) {
470: List categories = new ArrayList();
471: for (Iterator iter = localProperties.iterator(); iter.hasNext();) {
472: Property property = (Property) iter.next();
473: if (!categories.contains(property.getCategory()))
474: categories.add(property.getCategory());
475: }
476: return categories;
477: }
478:
479: /**
480: * Add the specified properties to the model using the specified parent.
481: *
482: * @param localProperties the properties to add to the end of the model
483: * @param parent the {@link Item} parent of these properties, null if none
484: */
485: private void addPropertiesToModel(List localProperties, Item parent) {
486: for (Iterator iter = localProperties.iterator(); iter.hasNext();) {
487: Property property = (Property) iter.next();
488: Item propertyItem = new Item(property, parent);
489: model.add(propertyItem);
490:
491: // add any sub-properties
492: Property[] subProperties = property.getSubProperties();
493: if (subProperties != null && subProperties.length > 0)
494: addPropertiesToModel(Arrays.asList(subProperties),
495: propertyItem);
496: }
497: }
498:
499: /**
500: * Convenience method to get all the properties of one category.
501: */
502: private List getPropertiesForCategory(List localProperties,
503: String category) {
504: List categoryProperties = new ArrayList();
505: for (Iterator iter = localProperties.iterator(); iter.hasNext();) {
506: Property property = (Property) iter.next();
507: if ((category == property.getCategory())
508: || (category != null && category.equals(property
509: .getCategory()))) {
510: categoryProperties.add(property);
511: }
512: }
513: return categoryProperties;
514: }
515:
516: public class Item {
517: private String name;
518: private Property property;
519: private Item parent;
520: private boolean hasToggle = true;
521: private boolean visible = true;
522:
523: private Item(String name, Item parent) {
524: this .name = name;
525: this .parent = parent;
526: // this is not a property but a category, always has toggle
527: this .hasToggle = true;
528: }
529:
530: private Item(Property property, Item parent) {
531: this .name = property.getDisplayName();
532: this .property = property;
533: this .parent = parent;
534: this .visible = (property == null);
535:
536: // properties toggle if there are sub-properties
537: Property[] subProperties = property.getSubProperties();
538: hasToggle = subProperties != null
539: && subProperties.length > 0;
540: }
541:
542: public String getName() {
543: return name;
544: }
545:
546: public boolean isProperty() {
547: return property != null;
548: }
549:
550: public Property getProperty() {
551: return property;
552: }
553:
554: public Item getParent() {
555: return parent;
556: }
557:
558: public int getDepth() {
559: int depth = 0;
560: if (parent != null) {
561: depth = parent.getDepth();
562: if (parent.isProperty())
563: ++depth;
564: }
565: return depth;
566: }
567:
568: public boolean hasToggle() {
569: return hasToggle;
570: }
571:
572: public void toggle() {
573: if (hasToggle()) {
574: visible = !visible;
575: visibilityChanged(false);
576: fireTableDataChanged();
577: }
578: }
579:
580: public void setVisible(final boolean visible) {
581: this .visible = visible;
582: }
583:
584: public boolean isVisible() {
585: return (parent == null || parent.isVisible())
586: && (!hasToggle || visible);
587: }
588:
589: public String getKey() {
590: StringBuffer key = new StringBuffer(name);
591: Item itemParent = parent;
592: while (itemParent != null) {
593: key.append(":");
594: key.append(itemParent.getName());
595: itemParent = itemParent.getParent();
596: }
597: return key.toString();
598: }
599:
600: }
601:
602: /**
603: * The default comparator for Properties. Used if no other comparator is
604: * defined.
605: */
606: public static class PropertyComparator implements Comparator {
607: public int compare(Object o1, Object o2) {
608: if (o1 instanceof Property && o2 instanceof Property) {
609: Property prop1 = (Property) o1;
610: Property prop2 = (Property) o2;
611: if (prop1 == null) {
612: return prop2 == null ? 0 : -1;
613: } else {
614: return STRING_COMPARATOR.compare(prop1
615: .getDisplayName() == null ? null : prop1
616: .getDisplayName().toLowerCase(), prop2
617: .getDisplayName() == null ? null : prop2
618: .getDisplayName().toLowerCase());
619: }
620: } else {
621: return 0;
622: }
623: }
624: }
625:
626: private static final Comparator STRING_COMPARATOR = new NaturalOrderStringComparator();
627:
628: public static class NaturalOrderStringComparator implements
629: Comparator {
630: public int compare(Object o1, Object o2) {
631: String s1 = (String) o1;
632: String s2 = (String) o2;
633: if (s1 == null) {
634: return s2 == null ? 0 : -1;
635: } else {
636: if (s2 == null) {
637: return 1;
638: } else {
639: return s1.compareTo(s2);
640: }
641: }
642: }
643: }
644: }
|