001: package snow.sortabletable;
002:
003: import javax.swing.table.*;
004: import java.util.*;
005: import javax.swing.event.*;
006: import javax.swing.*;
007: import java.awt.event.*;
008: import java.util.regex.*;
009:
010: /** This table model define a new TableModelChangeListener
011: this model should be used with the SortableTableModel to allow
012: keeping the selection when table model changes.
013:
014: HOWTO use:
015: + by each modification of this model (i.e. in SetValue())
016: First call before change : fireModelWillChange()
017: After change : fireTableDataChanged() and fireModelHasChanged()
018: */
019: public abstract class FineGrainTableModel extends AbstractTableModel {
020: private final Vector<TableModelChangeListener> modelListeners = new Vector<TableModelChangeListener>();
021:
022: public FineGrainTableModel() {
023: }
024:
025: /** Important, this allow the table to determine which renderer to use
026: (checkbox for Boolean, ...)
027: */
028: @Override
029: public Class getColumnClass(int column) {
030: if (this .getColumnCount() == 0 || this .getRowCount() == 0) {
031: return Object.class.getClass();
032: }
033:
034: Object val = this .getValueAt(0, column);
035: if (val == null)
036: return String.class;
037: return val.getClass();
038: }
039:
040: /** To sort columns... owerwrite if you need a better (or faster) sorting
041: */
042: @SuppressWarnings("unchecked")
043: public int compareForColumnSort(int pos1, int pos2, int col) {
044: Object val1 = this .getValueAt(pos1, col);
045: Object val2 = this .getValueAt(pos2, col);
046:
047: if (val1 instanceof Comparable
048: && val1.getClass() == val2.getClass()) {
049: return ((Comparable) val1).compareTo(val2);
050: } else if (val1 instanceof Boolean && val2 instanceof Boolean) {
051: return compareBooleans((Boolean) val1, (Boolean) val2);
052: } else {
053: // compare as a String
054: return ("" + val1.toString()).compareTo(this .getValueAt(
055: pos2, col).toString());
056: }
057: }
058:
059: /** @return true if the txt appears in the given row
060:
061: overwrite this method if you want a special search (Approximate, Numerical, ...)
062: @param p if not null, regex search p should be used.
063: *
064: public boolean hitForTextSearch(int row, String txt, Pattern p)
065: {
066: if(txt==null || txt.equals("")) return true;
067: String search = txt.toUpperCase();
068:
069: for(int col=0; col<this.getColumnCount(); col++)
070: {
071: String val = this.getValueAt(row,col).toString();
072: if(p!=null)
073: {
074: Matcher m = p.matcher(val);
075: return m.matches();
076: }
077: else
078: {
079: if(val.toUpperCase().indexOf(search)>=0) return true;
080: }
081: }
082: return false;
083: }*/
084:
085: /** @return true if the txt appears in the given row and column
086: * if column is -1, searches in all cols
087: */
088: @tide.annotations.Recurse
089: boolean hitForTextSearch(int row, int column, String txt,
090: Query query) {
091: if (txt == null || txt.equals(""))
092: return true;
093:
094: if (column == -1) {
095: for (int col = 0; col < this .getColumnCount(); col++) {
096: if (hitForTextSearch(row, col, txt, query))
097: return true;
098: }
099: return false;
100: }
101:
102: String search = txt.toUpperCase();
103: String val = this .getValueAsStringAt(row, column);
104: switch (query.comparison) {
105: case Contains:
106: if (val.indexOf(search) >= 0) {
107: return true;
108: }
109: break;
110: case Equals:
111: if (val.equals(search)) {
112: return true;
113: }
114: break;
115: case StartsWith:
116: if (val.startsWith(search)) {
117: return true;
118: }
119: break;
120: case EndsWith:
121: if (val.endsWith(search)) {
122: return true;
123: }
124: break;
125: case RegEx:
126: if (query.pattern.matcher(val).matches()) {
127: return true;
128: }
129: break;
130: }
131: return false;
132: }
133:
134: /**
135: * for the multi search (can be boosted)
136: */
137: public final boolean hitForTextSearch(int row, final Query[] queries) {
138: if (queries == null) {
139: return true;
140: }
141:
142: // pre eval (first and second order boolean bindings (not / and)
143: final List<Boolean> matches = new ArrayList<Boolean>();
144:
145: for (final Query q : queries) {
146: // ignore empty queries
147: if (q.isEmpty()) {
148: continue;
149: }
150:
151: boolean hit = hitForTextSearch(row, q.column, q.searchTxt,
152: q);
153:
154: if (q.isNegation()) {
155: hit = !hit;
156: }
157:
158: if (matches.size() > 0
159: && (q.boolOp == Query.Combine.And || q.boolOp == Query.Combine.AndNot)) {
160: // and
161: matches.set(matches.size() - 1, matches.get(matches
162: .size() - 1)
163: && hit);
164: } else // first criteria or OR operation
165: {
166: matches.add(hit);
167: }
168: }
169:
170: if (matches.isEmpty())
171: return true; // empty query...
172:
173: // eval (third order boolean binding (=or)
174: boolean match = false;
175: for (Boolean b : matches) {
176: match = match || b;
177: }
178:
179: return match;
180: }
181:
182: /** overwrite this is you want to specify a default width... (measured in Label.font size).
183: -1 do nothing. Any positive value will be used as preferred width.
184:
185: To be effective, the table must be set in
186: table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
187: before call installGUI()
188: */
189: public int getPreferredColumnWidth(int column) {
190: return -1;
191: }
192:
193: public int getColumnAlignment(int column) {
194: return JLabel.LEFT;
195: }
196:
197: /**
198: * returns a whole table row as a String array (in UPPERCASE)
199: * for the multi search
200: *
201: protected String[] getRowAsStrings(int row)
202: {
203: String[] strArr = new String[this.getColumnCount()];
204: for (int i=0; i<this.getColumnCount(); i++)
205: {
206: strArr[i] = this.getValueAsStringAt(row, i);
207: }
208: return strArr;
209: }*/
210:
211: /**
212: * returns the value of a cell as a String
213: * for the multi search.
214: * this method may be overwritten if the normal
215: * toString() of a value is not suitable for a search (e.g. dates)
216: */
217: public String getValueAsStringAt(int row, int col) {
218: return ("" + getValueAt(row, col)).toUpperCase();
219: }
220:
221: // Selection
222: //
223: // These four methods are not abstract,
224: // public void setRowSelection(int row, boolean isSelected)
225: // public boolean isRowSelected(int row)
226: // public void clearRowSelection()
227: // public int[] getSelectedRows()
228: //
229: // Here implemented is a non persistant selection mechanism.
230: // overwrite it to provide a selection that is stored in your model
231: //
232: //
233:
234: private HashSet<Integer> selectedRows = new HashSet<Integer>();
235:
236: /** used to select/deselect a given row.
237: this method doesn't fire any events
238: overwrite this AND THE TWO OTHERS selection method to provide your
239: selection mechanism. (maybe storing it in the model items)
240:
241: public void setRowSelection(int row, boolean isSelected)
242: public boolean isRowSelected(int row)
243: public void clearRowSelection()
244:
245:
246: EACH model row deletion will destroy sense of this selection.
247: => MUST NORMALLY BY OVERWRITTEN
248: + for bosting big tables: public int[] getSelectedRows()
249: */
250: public void setRowSelection(int row, boolean isSelected) {
251: if (isSelected) {
252: selectedRows.add(row);
253: } else {
254: selectedRows.remove(row);
255: }
256: }
257:
258: /** tells if the row is selected
259: */
260: public boolean isRowSelected(int row) {
261: return selectedRows.contains(row);
262: }
263:
264: public void clearRowSelection() {
265: for (int i = 0; i < this .getRowCount(); i++) {
266: this .setRowSelection(i, false);
267: }
268: }
269:
270: /** overwrite only if you want to boost.
271: This method use isRowSelected(row) to detect which rows are selected.
272: */
273: public int[] getSelectedRows() {
274: // slow but robust
275: //
276: Vector<Integer> sel = new Vector<Integer>();
277: synchronized (this ) {
278: for (int i = 0; i < getRowCount(); i++) {
279: if (isRowSelected(i))
280: sel.add(i);
281: }
282: int[] rep = new int[sel.size()];
283: for (int i = 0; i < rep.length; i++) {
284: rep[i] = sel.get(i);
285: }
286: return rep;
287: }
288:
289: }
290:
291: // Model Listener
292: //
293: public void addModelChangeListener(TableModelChangeListener listener) {
294: modelListeners.add(listener);
295: }
296:
297: public void removeModelChangeListener(
298: TableModelChangeListener listener) {
299: modelListeners.remove(listener);
300: }
301:
302: public void fireTableModelWillChange() {
303: TableModelChangeListener[] tml = null;
304: synchronized (modelListeners) {
305: // do a copy of the listeners to decouple them from the vector
306: // because they may perform operation on the vector in the notify loop (remove listener)
307: // and caus concurrent modification exceptions
308: tml = modelListeners
309: .toArray(new TableModelChangeListener[modelListeners
310: .size()]);
311: }
312: for (int i = tml.length - 1; i >= 0; i--) // last added first
313: {
314: tml[i].tableModelWillChange(new ChangeEvent(this ));
315: }
316: }
317:
318: public void terminate() {
319: modelListeners.clear();
320: selectedRows.clear();
321: }
322:
323: public void fireTableModelHasChanged() {
324: TableModelChangeListener[] tml = null;
325: synchronized (modelListeners) {
326: // do a copy of the listeners to decouple them from the vector
327: // because they may perform operation on the vector in the notify loop (remove listener)
328: // and caus concurrent modification exceptions
329: tml = modelListeners
330: .toArray(new TableModelChangeListener[modelListeners
331: .size()]);
332: }
333: for (int i = tml.length - 1; i >= 0; i--) // last added first
334: {
335: tml[i].tableModelHasChanged(new ChangeEvent(this ));
336: }
337: }
338:
339: // Some utilities for comparaison
340: //
341:
342: public static int compareInts(int i1, int i2) {
343: if (i1 == i2)
344: return 0;
345: if (i1 > i2)
346: return 1;
347: return -1;
348: }
349:
350: /** same as Double.valueOf(i1).compareTo(i2), but 5 times quicker if one has not the Double class already available.
351: * Use this also for ints...
352: */
353: public static int compareDoubles(double i1, double i2) {
354: if (i1 == i2)
355: return 0;
356: if (i1 > i2)
357: return 1;
358: return -1;
359: }
360:
361: /** equivalent to Boolean.valueOf(b1).compareTo(b2) but 3 times quicker
362: */
363: public static int compareBooleans(boolean b1, boolean b2) {
364: if (b1 == b2)
365: return 0;
366: if (b1)
367: return 1;
368: return -1;
369: }
370:
371: public static void main(String[] arguments) {
372: Test.main(null);
373: }
374:
375: }
|