View Javadoc

1   /*
2    * Copyright (c) 2003-2008 by Cosylab d. d.
3    *
4    * This file is part of CosyBeans-Common.
5    *
6    * CosyBeans-Common is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * CosyBeans-Common is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with CosyBeans-Common.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  
20  package com.cosylab.gui.components.table;
21  
22  import com.cosylab.logging.DebugLogger;
23  
24  import com.cosylab.util.ObjectList;
25  
26  import java.util.LinkedList;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  
30  import javax.swing.SwingUtilities;
31  import javax.swing.event.TableModelEvent;
32  import javax.swing.event.TableModelListener;
33  import javax.swing.table.TableModel;
34  
35  
36  /**
37   * <code>QueueTableModelInterceptor</code> wraps around <code>TableModel</code>
38   * and intercepts update events. All events are queued and dispatched in AWT
39   * thread. Similar events are integrated into one event. This makes updates
40   * from delegate GUI thread safe and prevents table to be updated unnecessary.
41   * 
42   * <p>
43   * Tu use interceptor, create your model, create interceptor and pass your
44   * model as parameter, then add to the table interceptor as model. Listen to
45   * model events directly on your model and not on interceptor or on model,
46   * which is rerturned from the table (except if <code>QueueTable</code>
47   * becouse it completly hides the interceptor).
48   * </p>
49   *
50   * @author <a href="mailto:igor.kriznar@cosylab.com">Igor Kriznar</a>
51   * @version $Id: QueueTableModelInterceptor.java,v 1.11 2008-08-25 11:18:22 jbobnar Exp $
52   *
53   * @since May 3, 2004.
54   */
55  public class QueueTableModelInterceptor implements TableModel,
56  	TableModelListener, Runnable
57  {
58  	private final TableModel delegate;
59  	private ObjectList listeners = new ObjectList(TableModelListener.class);
60  	private final LinkedList<TableModelEvent> queue = new LinkedList<TableModelEvent>();
61  	private final Logger logger = DebugLogger.getLogger("QTMI", Level.OFF);
62  	private Interceptor updater;
63  	private int relaxationTime=1000;
64  	private boolean destroyed=false;
65  
66  	class Interceptor extends Thread
67  	{
68  		long last = System.currentTimeMillis();
69  		boolean update = false;
70  
71  		public Interceptor() {
72  			super();
73  		}
74  		/**
75  		 * DOCUMENT ME!
76  		 */
77  		public synchronized void run()
78  		{
79  			while (!destroyed) {
80  				long t = System.currentTimeMillis();
81  
82  				if (update) {
83  					if (t - last > relaxationTime || t-last + 1 <= 0) {
84  						SwingUtilities.invokeLater(QueueTableModelInterceptor.this);
85  						last = t;
86  						update = false;
87  					} else {
88  						try {
89  							wait(t - last + 1);
90  						} catch (Exception e) {
91  							e.printStackTrace();
92  						}
93  					}
94  				} else {
95  					try {
96  						wait(0);
97  					} catch (Exception e) {
98  						e.printStackTrace();
99  					}
100 				}
101 			}
102 		}
103 
104 		/**
105 		 * DOCUMENT ME!
106 		 */
107 		public synchronized void update()
108 		{
109 			update = true;
110 			notifyAll();
111 		}
112 	}
113 
114 	/**
115 	 * Creates a new QueueTableModelInterceptor object.
116 	 *
117 	 * @param delegate the wrapped model
118 	 */
119 	public QueueTableModelInterceptor(TableModel delegate)
120 	{
121 		//assert (delegate != null);
122 		this.delegate = delegate;
123 
124 		delegate.addTableModelListener(this);
125 		
126 		updater = new Interceptor();
127 		updater.start();
128 	}
129 
130 	/* (non-Javadoc)
131 	 * @see javax.swing.table.TableModel#getColumnCount()
132 	 */
133 	public int getColumnCount()
134 	{
135 		return delegate.getColumnCount();
136 	}
137 
138 	/* (non-Javadoc)
139 	 * @see javax.swing.table.TableModel#getRowCount()
140 	 */
141 	public int getRowCount()
142 	{
143 		return delegate.getRowCount();
144 	}
145 
146 	/* (non-Javadoc)
147 	 * @see javax.swing.table.TableModel#isCellEditable(int, int)
148 	 */
149 	public boolean isCellEditable(int rowIndex, int columnIndex)
150 	{
151 		return delegate.isCellEditable(rowIndex, columnIndex);
152 	}
153 
154 	/* (non-Javadoc)
155 	 * @see javax.swing.table.TableModel#getColumnClass(int)
156 	 */
157 	public Class<?> getColumnClass(int columnIndex)
158 	{
159 		return delegate.getColumnClass(columnIndex);
160 	}
161 
162 	/* (non-Javadoc)
163 	 * @see javax.swing.table.TableModel#getValueAt(int, int)
164 	 */
165 	public Object getValueAt(int rowIndex, int columnIndex)
166 	{
167 		return delegate.getValueAt(rowIndex, columnIndex);
168 	}
169 
170 	/* (non-Javadoc)
171 	 * @see javax.swing.table.TableModel#setValueAt(java.lang.Object, int, int)
172 	 */
173 	public void setValueAt(Object aValue, int rowIndex, int columnIndex)
174 	{
175 		delegate.setValueAt(aValue, rowIndex, columnIndex);
176 	}
177 
178 	/* (non-Javadoc)
179 	 * @see javax.swing.table.TableModel#getColumnName(int)
180 	 */
181 	public String getColumnName(int columnIndex)
182 	{
183 		return delegate.getColumnName(columnIndex);
184 	}
185 
186 	/* (non-Javadoc)
187 	 * @see javax.swing.table.TableModel#addTableModelListener(javax.swing.event.TableModelListener)
188 	 */
189 	public void addTableModelListener(TableModelListener l)
190 	{
191 		listeners.add(l);
192 	}
193 
194 	/* (non-Javadoc)
195 	 * @see javax.swing.table.TableModel#removeTableModelListener(javax.swing.event.TableModelListener)
196 	 */
197 	public void removeTableModelListener(TableModelListener l)
198 	{
199 		listeners.remove(l);
200 	}
201 
202 	/**
203 	 * The model which is wrapped by this interceptor.
204 	 *
205 	 * @return
206 	 */
207 	public TableModel getDelegate()
208 	{
209 		return delegate;
210 	}
211 
212 	/* (non-Javadoc)
213 	 * @see javax.swing.event.TableModelListener#tableChanged(javax.swing.event.TableModelEvent)
214 	 */
215 	public void tableChanged(TableModelEvent e)
216 	{
217 		enqueue(e);
218 	}
219 
220 	protected void fire(TableModelEvent e)
221 	{
222 		logger.fine("column= " + e.getColumn() + " row= " + e.getFirstRow()
223 		    + "-" + e.getLastRow() + " type= " + e.getType());
224 
225 		TableModelListener[] l = (TableModelListener[])listeners.toArray();
226 
227 		for (int i = 0; i < l.length; i++) {
228 			try {
229 				l[i].tableChanged(e);
230 			} catch (Exception ex) {
231 				ex.printStackTrace();
232 			}
233 		}
234 	}
235 
236 	protected synchronized void enqueue(TableModelEvent e)
237 	{
238 		logger.fine("adding column= " + e.getColumn() + " row= "
239 		    + e.getFirstRow() + "-" + e.getLastRow() + " type= " + e.getType());
240 
241 		TableModelEvent ev = queue.size() > 0
242 			? (TableModelEvent)queue.getLast() : null;
243 
244 		// now we try to merge last two events, if they are of same type
245 		if (ev != null
246 		    && (ev.getType() == e.getType()
247   	        //bugfix RT#10735 by jkamenik
248 		    //this would not work correctly if ev has not HEADER_ROW type event
249 		    //|| e.getType() == TableModelEvent.HEADER_ROW
250 			//end bugfix
251 		    || ev.getType() == TableModelEvent.HEADER_ROW)) {
252 			
253 		    int col = ev.getColumn();
254 
255 			if (col != e.getColumn()) {
256 				col = TableModelEvent.ALL_COLUMNS;
257 			}
258 
259 			int first = ev.getFirstRow();
260 
261 			if (first > e.getFirstRow()) {
262 				first = e.getFirstRow();
263 			}
264 
265 			int last = ev.getLastRow();
266 
267 			if (last < e.getLastRow() && last != TableModelEvent.HEADER_ROW) {
268 				last = e.getLastRow();
269 			}
270 
271 			//System.out.println("Merging ("+first+","+last+","+col+") from ("+ev.getFirstRow()+","+ev.getLastRow()+","+ev.getColumn()+") and ("+e.getFirstRow()+","+e.getLastRow()+","+e.getColumn()+")");
272 			// last check if we have actually generated new event
273 			if (col != ev.getColumn() || first != ev.getFirstRow()
274 			    || last != ev.getLastRow()) {
275 				queue.removeLast();
276 				logger.fine("added column= " + col + " row= " + first + "-"
277 				    + last + " type= " + ev.getType());
278 				queue.add(new TableModelEvent(this, first, last, col,
279 				        ev.getType()));
280 
281 				//System.out.println("Merged "+queue.size());
282 				/*System.out.println("Merged (" + first + "," + last + "," + col
283 				    + ") from (" + ev.getFirstRow() + "," + ev.getLastRow()
284 				    + "," + ev.getColumn() + ") and (" + e.getFirstRow() + ","
285 				    + e.getLastRow() + "," + e.getColumn() + ")");*/
286 				updater.update();
287 			}
288 		} else {
289 			// new event added to the queue
290 			logger.fine("added column= " + e.getColumn() + " row= "
291 			    + e.getFirstRow() + "-" + e.getLastRow() + " type= "
292 			    + e.getType());
293 			queue.add(e);
294 
295 			updater.update();
296 		}
297 	}
298 
299 	protected synchronized TableModelEvent dequeue()
300 	{
301 		return queue.size() > 0 ? queue.removeFirst() : null;
302 	}
303 
304 	/* (non-Javadoc)
305 	 * @see java.lang.Runnable#run()
306 	 */
307 	public void run()
308 	{
309 		TableModelEvent e;
310 
311 		while ((e = dequeue()) != null) {
312 			//System.out.println("Update ("+e.getFirstRow()+","+e.getLastRow()+","+e.getColumn()+")");
313 			if (e.getSource() == this) {
314 				fire(e);
315 			} else {
316 				fire(new TableModelEvent(this, e.getFirstRow(), e.getLastRow(),
317 				        e.getColumn(), e.getType()));
318 			}
319 		}
320 	}
321 
322 	/**
323 	 * Returns minimum time betwean two table updates interceptor will wait.
324 	 * @return minimum time betwean two table updates 
325 	 */
326 	public int getRelaxationTime() {
327 		return relaxationTime;
328 	}
329 
330 	/**
331 	 * Sets minimum time betwean two table updates interceptor will wait.
332 	 * @param relaxationTime
333 	 */
334 	public void setRelaxationTime(int relaxationTime) {
335 		this.relaxationTime = relaxationTime;
336 		updater.update();
337 	}
338 
339 	/**
340 	 * Returns <code>true</code> after destroy was called.
341 	 * @return <code>true</code> if destroyed
342 	 */
343 	public boolean isDestroyed() {
344 		return destroyed;
345 	}
346 	
347 	/**
348 	 * Clears internal interceptor thread.
349 	 *
350 	 */
351 	public void destroy() {
352 		destroyed=true;
353 		updater.update();
354 		
355 		delegate.removeTableModelListener(this);
356 		listeners.clear();
357 		queue.clear();
358 	}
359 }
360 
361 /* __oOo__ */